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