1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/browser/accessibility/browser_accessibility.h" 6 7 #include "base/logging.h" 8 #include "base/strings/string_number_conversions.h" 9 #include "base/strings/string_util.h" 10 #include "base/strings/utf_string_conversions.h" 11 #include "content/browser/accessibility/browser_accessibility_manager.h" 12 #include "content/common/accessibility_messages.h" 13 14 namespace content { 15 16 #if !defined(OS_MACOSX) && \ 17 !defined(OS_WIN) && \ 18 !defined(OS_ANDROID) 19 // We have subclassess of BrowserAccessibility on Mac and Win. For any other 20 // platform, instantiate the base class. 21 // static 22 BrowserAccessibility* BrowserAccessibility::Create() { 23 return new BrowserAccessibility(); 24 } 25 #endif 26 27 BrowserAccessibility::BrowserAccessibility() 28 : manager_(NULL), 29 node_(NULL) { 30 } 31 32 BrowserAccessibility::~BrowserAccessibility() { 33 } 34 35 void BrowserAccessibility::Init(BrowserAccessibilityManager* manager, 36 ui::AXNode* node) { 37 manager_ = manager; 38 node_ = node; 39 } 40 41 void BrowserAccessibility::OnDataChanged() { 42 GetStringAttribute(ui::AX_ATTR_NAME, &name_); 43 GetStringAttribute(ui::AX_ATTR_VALUE, &value_); 44 } 45 46 bool BrowserAccessibility::PlatformIsLeaf() const { 47 if (InternalChildCount() == 0) 48 return true; 49 50 // All of these roles may have children that we use as internal 51 // implementation details, but we want to expose them as leaves 52 // to platform accessibility APIs. 53 switch (GetRole()) { 54 case ui::AX_ROLE_EDITABLE_TEXT: 55 case ui::AX_ROLE_SLIDER: 56 case ui::AX_ROLE_STATIC_TEXT: 57 case ui::AX_ROLE_TEXT_AREA: 58 case ui::AX_ROLE_TEXT_FIELD: 59 return true; 60 default: 61 return false; 62 } 63 } 64 65 uint32 BrowserAccessibility::PlatformChildCount() const { 66 return PlatformIsLeaf() ? 0 : InternalChildCount(); 67 } 68 69 bool BrowserAccessibility::IsNative() const { 70 return false; 71 } 72 73 bool BrowserAccessibility::IsDescendantOf( 74 BrowserAccessibility* ancestor) { 75 if (this == ancestor) { 76 return true; 77 } else if (GetParent()) { 78 return GetParent()->IsDescendantOf(ancestor); 79 } 80 81 return false; 82 } 83 84 BrowserAccessibility* BrowserAccessibility::PlatformGetChild( 85 uint32 child_index) const { 86 DCHECK(child_index < InternalChildCount()); 87 BrowserAccessibility* result = InternalGetChild(child_index); 88 89 if (result->HasBoolAttribute(ui::AX_ATTR_IS_AX_TREE_HOST)) { 90 BrowserAccessibilityManager* child_manager = 91 manager_->delegate()->AccessibilityGetChildFrame(result->GetId()); 92 if (child_manager) 93 result = child_manager->GetRoot(); 94 } 95 96 return result; 97 } 98 99 BrowserAccessibility* BrowserAccessibility::GetPreviousSibling() { 100 if (GetParent() && GetIndexInParent() > 0) 101 return GetParent()->InternalGetChild(GetIndexInParent() - 1); 102 103 return NULL; 104 } 105 106 BrowserAccessibility* BrowserAccessibility::GetNextSibling() { 107 if (GetParent() && 108 GetIndexInParent() >= 0 && 109 GetIndexInParent() < static_cast<int>( 110 GetParent()->InternalChildCount() - 1)) { 111 return GetParent()->InternalGetChild(GetIndexInParent() + 1); 112 } 113 114 return NULL; 115 } 116 117 uint32 BrowserAccessibility::InternalChildCount() const { 118 if (!node_ || !manager_) 119 return 0; 120 return static_cast<uint32>(node_->child_count()); 121 } 122 123 BrowserAccessibility* BrowserAccessibility::InternalGetChild( 124 uint32 child_index) const { 125 if (!node_ || !manager_) 126 return NULL; 127 return manager_->GetFromAXNode(node_->children()[child_index]); 128 } 129 130 BrowserAccessibility* BrowserAccessibility::GetParent() const { 131 if (!node_ || !manager_) 132 return NULL; 133 ui::AXNode* parent = node_->parent(); 134 if (parent) 135 return manager_->GetFromAXNode(parent); 136 137 if (!manager_->delegate()) 138 return NULL; 139 140 BrowserAccessibility* host_node = 141 manager_->delegate()->AccessibilityGetParentFrame(); 142 if (!host_node) 143 return NULL; 144 145 return host_node->GetParent(); 146 } 147 148 int32 BrowserAccessibility::GetIndexInParent() const { 149 return node_ ? node_->index_in_parent() : -1; 150 } 151 152 int32 BrowserAccessibility::GetId() const { 153 return node_ ? node_->id() : -1; 154 } 155 156 const ui::AXNodeData& BrowserAccessibility::GetData() const { 157 CR_DEFINE_STATIC_LOCAL(ui::AXNodeData, empty_data, ()); 158 if (node_) 159 return node_->data(); 160 else 161 return empty_data; 162 } 163 164 gfx::Rect BrowserAccessibility::GetLocation() const { 165 return GetData().location; 166 } 167 168 int32 BrowserAccessibility::GetRole() const { 169 return GetData().role; 170 } 171 172 int32 BrowserAccessibility::GetState() const { 173 return GetData().state; 174 } 175 176 const BrowserAccessibility::HtmlAttributes& 177 BrowserAccessibility::GetHtmlAttributes() const { 178 return GetData().html_attributes; 179 } 180 181 gfx::Rect BrowserAccessibility::GetLocalBoundsRect() const { 182 gfx::Rect bounds = GetLocation(); 183 184 // Walk up the parent chain. Every time we encounter a Web Area, offset 185 // based on the scroll bars and then offset based on the origin of that 186 // nested web area. 187 BrowserAccessibility* parent = GetParent(); 188 bool need_to_offset_web_area = 189 (GetRole() == ui::AX_ROLE_WEB_AREA || 190 GetRole() == ui::AX_ROLE_ROOT_WEB_AREA); 191 while (parent) { 192 if (need_to_offset_web_area && 193 parent->GetLocation().width() > 0 && 194 parent->GetLocation().height() > 0) { 195 bounds.Offset(parent->GetLocation().x(), parent->GetLocation().y()); 196 need_to_offset_web_area = false; 197 } 198 199 // On some platforms, we don't want to take the root scroll offsets 200 // into account. 201 if (parent->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA && 202 !manager()->UseRootScrollOffsetsWhenComputingBounds()) { 203 break; 204 } 205 206 if (parent->GetRole() == ui::AX_ROLE_WEB_AREA || 207 parent->GetRole() == ui::AX_ROLE_ROOT_WEB_AREA) { 208 int sx = 0; 209 int sy = 0; 210 if (parent->GetIntAttribute(ui::AX_ATTR_SCROLL_X, &sx) && 211 parent->GetIntAttribute(ui::AX_ATTR_SCROLL_Y, &sy)) { 212 bounds.Offset(-sx, -sy); 213 } 214 need_to_offset_web_area = true; 215 } 216 parent = parent->GetParent(); 217 } 218 219 return bounds; 220 } 221 222 gfx::Rect BrowserAccessibility::GetGlobalBoundsRect() const { 223 gfx::Rect bounds = GetLocalBoundsRect(); 224 225 // Adjust the bounds by the top left corner of the containing view's bounds 226 // in screen coordinates. 227 bounds.Offset(manager_->GetViewBounds().OffsetFromOrigin()); 228 229 return bounds; 230 } 231 232 gfx::Rect BrowserAccessibility::GetLocalBoundsForRange(int start, int len) 233 const { 234 if (GetRole() != ui::AX_ROLE_STATIC_TEXT) { 235 // Apply recursively to all static text descendants. For example, if 236 // you call it on a div with two text node children, it just calls 237 // GetLocalBoundsForRange on each of the two children (adjusting 238 // |start| for each one) and unions the resulting rects. 239 gfx::Rect bounds; 240 for (size_t i = 0; i < InternalChildCount(); ++i) { 241 BrowserAccessibility* child = InternalGetChild(i); 242 int child_len = child->GetStaticTextLenRecursive(); 243 if (start < child_len && start + len > 0) { 244 gfx::Rect child_rect = child->GetLocalBoundsForRange(start, len); 245 bounds.Union(child_rect); 246 } 247 start -= child_len; 248 } 249 return bounds; 250 } 251 252 int end = start + len; 253 int child_start = 0; 254 int child_end = 0; 255 256 gfx::Rect bounds; 257 for (size_t i = 0; i < InternalChildCount() && child_end < start + len; ++i) { 258 BrowserAccessibility* child = InternalGetChild(i); 259 DCHECK_EQ(child->GetRole(), ui::AX_ROLE_INLINE_TEXT_BOX); 260 std::string child_text; 261 child->GetStringAttribute(ui::AX_ATTR_VALUE, &child_text); 262 int child_len = static_cast<int>(child_text.size()); 263 child_start = child_end; 264 child_end += child_len; 265 266 if (child_end < start) 267 continue; 268 269 int overlap_start = std::max(start, child_start); 270 int overlap_end = std::min(end, child_end); 271 272 int local_start = overlap_start - child_start; 273 int local_end = overlap_end - child_start; 274 275 gfx::Rect child_rect = child->GetLocation(); 276 int text_direction = child->GetIntAttribute( 277 ui::AX_ATTR_TEXT_DIRECTION); 278 const std::vector<int32>& character_offsets = child->GetIntListAttribute( 279 ui::AX_ATTR_CHARACTER_OFFSETS); 280 int start_pixel_offset = 281 local_start > 0 ? character_offsets[local_start - 1] : 0; 282 int end_pixel_offset = 283 local_end > 0 ? character_offsets[local_end - 1] : 0; 284 285 gfx::Rect child_overlap_rect; 286 switch (text_direction) { 287 case ui::AX_TEXT_DIRECTION_NONE: 288 case ui::AX_TEXT_DIRECTION_LR: { 289 int left = child_rect.x() + start_pixel_offset; 290 int right = child_rect.x() + end_pixel_offset; 291 child_overlap_rect = gfx::Rect(left, child_rect.y(), 292 right - left, child_rect.height()); 293 break; 294 } 295 case ui::AX_TEXT_DIRECTION_RL: { 296 int right = child_rect.right() - start_pixel_offset; 297 int left = child_rect.right() - end_pixel_offset; 298 child_overlap_rect = gfx::Rect(left, child_rect.y(), 299 right - left, child_rect.height()); 300 break; 301 } 302 case ui::AX_TEXT_DIRECTION_TB: { 303 int top = child_rect.y() + start_pixel_offset; 304 int bottom = child_rect.y() + end_pixel_offset; 305 child_overlap_rect = gfx::Rect(child_rect.x(), top, 306 child_rect.width(), bottom - top); 307 break; 308 } 309 case ui::AX_TEXT_DIRECTION_BT: { 310 int bottom = child_rect.bottom() - start_pixel_offset; 311 int top = child_rect.bottom() - end_pixel_offset; 312 child_overlap_rect = gfx::Rect(child_rect.x(), top, 313 child_rect.width(), bottom - top); 314 break; 315 } 316 default: 317 NOTREACHED(); 318 } 319 320 if (bounds.width() == 0 && bounds.height() == 0) 321 bounds = child_overlap_rect; 322 else 323 bounds.Union(child_overlap_rect); 324 } 325 326 return bounds; 327 } 328 329 gfx::Rect BrowserAccessibility::GetGlobalBoundsForRange(int start, int len) 330 const { 331 gfx::Rect bounds = GetLocalBoundsForRange(start, len); 332 333 // Adjust the bounds by the top left corner of the containing view's bounds 334 // in screen coordinates. 335 bounds.Offset(manager_->GetViewBounds().OffsetFromOrigin()); 336 337 return bounds; 338 } 339 340 BrowserAccessibility* BrowserAccessibility::BrowserAccessibilityForPoint( 341 const gfx::Point& point) { 342 // The best result found that's a child of this object. 343 BrowserAccessibility* child_result = NULL; 344 // The best result that's an indirect descendant like grandchild, etc. 345 BrowserAccessibility* descendant_result = NULL; 346 347 // Walk the children recursively looking for the BrowserAccessibility that 348 // most tightly encloses the specified point. Walk backwards so that in 349 // the absence of any other information, we assume the object that occurs 350 // later in the tree is on top of one that comes before it. 351 for (int i = static_cast<int>(PlatformChildCount()) - 1; i >= 0; --i) { 352 BrowserAccessibility* child = PlatformGetChild(i); 353 354 // Skip table columns because cells are only contained in rows, 355 // not columns. 356 if (child->GetRole() == ui::AX_ROLE_COLUMN) 357 continue; 358 359 if (child->GetGlobalBoundsRect().Contains(point)) { 360 BrowserAccessibility* result = child->BrowserAccessibilityForPoint(point); 361 if (result == child && !child_result) 362 child_result = result; 363 if (result != child && !descendant_result) 364 descendant_result = result; 365 } 366 367 if (child_result && descendant_result) 368 break; 369 } 370 371 // Explanation of logic: it's possible that this point overlaps more than 372 // one child of this object. If so, as a heuristic we prefer if the point 373 // overlaps a descendant of one of the two children and not the other. 374 // As an example, suppose you have two rows of buttons - the buttons don't 375 // overlap, but the rows do. Without this heuristic, we'd greedily only 376 // consider one of the containers. 377 if (descendant_result) 378 return descendant_result; 379 if (child_result) 380 return child_result; 381 382 return this; 383 } 384 385 void BrowserAccessibility::Destroy() { 386 // Allow the object to fire a TextRemoved notification. 387 name_.clear(); 388 value_.clear(); 389 390 manager_->NotifyAccessibilityEvent(ui::AX_EVENT_HIDE, this); 391 node_ = NULL; 392 manager_ = NULL; 393 394 NativeReleaseReference(); 395 } 396 397 void BrowserAccessibility::NativeReleaseReference() { 398 delete this; 399 } 400 401 bool BrowserAccessibility::HasBoolAttribute( 402 ui::AXBoolAttribute attribute) const { 403 const ui::AXNodeData& data = GetData(); 404 for (size_t i = 0; i < data.bool_attributes.size(); ++i) { 405 if (data.bool_attributes[i].first == attribute) 406 return true; 407 } 408 409 return false; 410 } 411 412 413 bool BrowserAccessibility::GetBoolAttribute( 414 ui::AXBoolAttribute attribute) const { 415 const ui::AXNodeData& data = GetData(); 416 for (size_t i = 0; i < data.bool_attributes.size(); ++i) { 417 if (data.bool_attributes[i].first == attribute) 418 return data.bool_attributes[i].second; 419 } 420 421 return false; 422 } 423 424 bool BrowserAccessibility::GetBoolAttribute( 425 ui::AXBoolAttribute attribute, bool* value) const { 426 const ui::AXNodeData& data = GetData(); 427 for (size_t i = 0; i < data.bool_attributes.size(); ++i) { 428 if (data.bool_attributes[i].first == attribute) { 429 *value = data.bool_attributes[i].second; 430 return true; 431 } 432 } 433 434 return false; 435 } 436 437 bool BrowserAccessibility::HasFloatAttribute( 438 ui::AXFloatAttribute attribute) const { 439 const ui::AXNodeData& data = GetData(); 440 for (size_t i = 0; i < data.float_attributes.size(); ++i) { 441 if (data.float_attributes[i].first == attribute) 442 return true; 443 } 444 445 return false; 446 } 447 448 float BrowserAccessibility::GetFloatAttribute( 449 ui::AXFloatAttribute attribute) const { 450 const ui::AXNodeData& data = GetData(); 451 for (size_t i = 0; i < data.float_attributes.size(); ++i) { 452 if (data.float_attributes[i].first == attribute) 453 return data.float_attributes[i].second; 454 } 455 456 return 0.0; 457 } 458 459 bool BrowserAccessibility::GetFloatAttribute( 460 ui::AXFloatAttribute attribute, float* value) const { 461 const ui::AXNodeData& data = GetData(); 462 for (size_t i = 0; i < data.float_attributes.size(); ++i) { 463 if (data.float_attributes[i].first == attribute) { 464 *value = data.float_attributes[i].second; 465 return true; 466 } 467 } 468 469 return false; 470 } 471 472 bool BrowserAccessibility::HasIntAttribute( 473 ui::AXIntAttribute attribute) const { 474 const ui::AXNodeData& data = GetData(); 475 for (size_t i = 0; i < data.int_attributes.size(); ++i) { 476 if (data.int_attributes[i].first == attribute) 477 return true; 478 } 479 480 return false; 481 } 482 483 int BrowserAccessibility::GetIntAttribute(ui::AXIntAttribute attribute) const { 484 const ui::AXNodeData& data = GetData(); 485 for (size_t i = 0; i < data.int_attributes.size(); ++i) { 486 if (data.int_attributes[i].first == attribute) 487 return data.int_attributes[i].second; 488 } 489 490 return 0; 491 } 492 493 bool BrowserAccessibility::GetIntAttribute( 494 ui::AXIntAttribute attribute, int* value) const { 495 const ui::AXNodeData& data = GetData(); 496 for (size_t i = 0; i < data.int_attributes.size(); ++i) { 497 if (data.int_attributes[i].first == attribute) { 498 *value = data.int_attributes[i].second; 499 return true; 500 } 501 } 502 503 return false; 504 } 505 506 bool BrowserAccessibility::HasStringAttribute( 507 ui::AXStringAttribute attribute) const { 508 const ui::AXNodeData& data = GetData(); 509 for (size_t i = 0; i < data.string_attributes.size(); ++i) { 510 if (data.string_attributes[i].first == attribute) 511 return true; 512 } 513 514 return false; 515 } 516 517 const std::string& BrowserAccessibility::GetStringAttribute( 518 ui::AXStringAttribute attribute) const { 519 const ui::AXNodeData& data = GetData(); 520 CR_DEFINE_STATIC_LOCAL(std::string, empty_string, ()); 521 for (size_t i = 0; i < data.string_attributes.size(); ++i) { 522 if (data.string_attributes[i].first == attribute) 523 return data.string_attributes[i].second; 524 } 525 526 return empty_string; 527 } 528 529 bool BrowserAccessibility::GetStringAttribute( 530 ui::AXStringAttribute attribute, std::string* value) const { 531 const ui::AXNodeData& data = GetData(); 532 for (size_t i = 0; i < data.string_attributes.size(); ++i) { 533 if (data.string_attributes[i].first == attribute) { 534 *value = data.string_attributes[i].second; 535 return true; 536 } 537 } 538 539 return false; 540 } 541 542 base::string16 BrowserAccessibility::GetString16Attribute( 543 ui::AXStringAttribute attribute) const { 544 std::string value_utf8; 545 if (!GetStringAttribute(attribute, &value_utf8)) 546 return base::string16(); 547 return base::UTF8ToUTF16(value_utf8); 548 } 549 550 bool BrowserAccessibility::GetString16Attribute( 551 ui::AXStringAttribute attribute, 552 base::string16* value) const { 553 std::string value_utf8; 554 if (!GetStringAttribute(attribute, &value_utf8)) 555 return false; 556 *value = base::UTF8ToUTF16(value_utf8); 557 return true; 558 } 559 560 void BrowserAccessibility::SetStringAttribute( 561 ui::AXStringAttribute attribute, const std::string& value) { 562 if (!node_) 563 return; 564 ui::AXNodeData data = GetData(); 565 for (size_t i = 0; i < data.string_attributes.size(); ++i) { 566 if (data.string_attributes[i].first == attribute) { 567 data.string_attributes[i].second = value; 568 node_->SetData(data); 569 return; 570 } 571 } 572 if (!value.empty()) { 573 data.string_attributes.push_back(std::make_pair(attribute, value)); 574 node_->SetData(data); 575 } 576 } 577 578 bool BrowserAccessibility::HasIntListAttribute( 579 ui::AXIntListAttribute attribute) const { 580 const ui::AXNodeData& data = GetData(); 581 for (size_t i = 0; i < data.intlist_attributes.size(); ++i) { 582 if (data.intlist_attributes[i].first == attribute) 583 return true; 584 } 585 586 return false; 587 } 588 589 const std::vector<int32>& BrowserAccessibility::GetIntListAttribute( 590 ui::AXIntListAttribute attribute) const { 591 const ui::AXNodeData& data = GetData(); 592 CR_DEFINE_STATIC_LOCAL(std::vector<int32>, empty_vector, ()); 593 for (size_t i = 0; i < data.intlist_attributes.size(); ++i) { 594 if (data.intlist_attributes[i].first == attribute) 595 return data.intlist_attributes[i].second; 596 } 597 598 return empty_vector; 599 } 600 601 bool BrowserAccessibility::GetIntListAttribute( 602 ui::AXIntListAttribute attribute, 603 std::vector<int32>* value) const { 604 const ui::AXNodeData& data = GetData(); 605 for (size_t i = 0; i < data.intlist_attributes.size(); ++i) { 606 if (data.intlist_attributes[i].first == attribute) { 607 *value = data.intlist_attributes[i].second; 608 return true; 609 } 610 } 611 612 return false; 613 } 614 615 bool BrowserAccessibility::GetHtmlAttribute( 616 const char* html_attr, std::string* value) const { 617 for (size_t i = 0; i < GetHtmlAttributes().size(); ++i) { 618 const std::string& attr = GetHtmlAttributes()[i].first; 619 if (LowerCaseEqualsASCII(attr, html_attr)) { 620 *value = GetHtmlAttributes()[i].second; 621 return true; 622 } 623 } 624 625 return false; 626 } 627 628 bool BrowserAccessibility::GetHtmlAttribute( 629 const char* html_attr, base::string16* value) const { 630 std::string value_utf8; 631 if (!GetHtmlAttribute(html_attr, &value_utf8)) 632 return false; 633 *value = base::UTF8ToUTF16(value_utf8); 634 return true; 635 } 636 637 bool BrowserAccessibility::GetAriaTristate( 638 const char* html_attr, 639 bool* is_defined, 640 bool* is_mixed) const { 641 *is_defined = false; 642 *is_mixed = false; 643 644 base::string16 value; 645 if (!GetHtmlAttribute(html_attr, &value) || 646 value.empty() || 647 EqualsASCII(value, "undefined")) { 648 return false; // Not set (and *is_defined is also false) 649 } 650 651 *is_defined = true; 652 653 if (EqualsASCII(value, "true")) 654 return true; 655 656 if (EqualsASCII(value, "mixed")) 657 *is_mixed = true; 658 659 return false; // Not set 660 } 661 662 bool BrowserAccessibility::HasState(ui::AXState state_enum) const { 663 return (GetState() >> state_enum) & 1; 664 } 665 666 bool BrowserAccessibility::IsEditableText() const { 667 // These roles don't have readonly set, but they're not editable text. 668 if (GetRole() == ui::AX_ROLE_SCROLL_AREA || 669 GetRole() == ui::AX_ROLE_COLUMN || 670 GetRole() == ui::AX_ROLE_TABLE_HEADER_CONTAINER) { 671 return false; 672 } 673 674 // Note: WebAXStateReadonly being false means it's either a text control, 675 // or contenteditable. We also check for editable text roles to cover 676 // another element that has role=textbox set on it. 677 return (!HasState(ui::AX_STATE_READ_ONLY) || 678 GetRole() == ui::AX_ROLE_TEXT_FIELD || 679 GetRole() == ui::AX_ROLE_TEXT_AREA); 680 } 681 682 std::string BrowserAccessibility::GetTextRecursive() const { 683 if (!name_.empty()) { 684 return name_; 685 } 686 687 std::string result; 688 for (uint32 i = 0; i < PlatformChildCount(); ++i) 689 result += PlatformGetChild(i)->GetTextRecursive(); 690 return result; 691 } 692 693 int BrowserAccessibility::GetStaticTextLenRecursive() const { 694 if (GetRole() == ui::AX_ROLE_STATIC_TEXT) 695 return static_cast<int>(GetStringAttribute(ui::AX_ATTR_VALUE).size()); 696 697 int len = 0; 698 for (size_t i = 0; i < InternalChildCount(); ++i) 699 len += InternalGetChild(i)->GetStaticTextLenRecursive(); 700 return len; 701 } 702 703 } // namespace content 704