1/* 2 * Copyright (C) 2008, 2009, 2010 Apple Inc. All rights reserved. 3 * 4 * Redistribution and use in source and binary forms, with or without 5 * modification, are permitted provided that the following conditions 6 * are met: 7 * 8 * 1. Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * 2. Redistributions in binary form must reproduce the above copyright 11 * notice, this list of conditions and the following disclaimer in the 12 * documentation and/or other materials provided with the distribution. 13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of 14 * its contributors may be used to endorse or promote products derived 15 * from this software without specific prior written permission. 16 * 17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY 18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 27 */ 28 29#include "config.h" 30 31#if HAVE(ACCESSIBILITY) 32 33#include "AXObjectCache.h" 34 35#include "AccessibilityARIAGrid.h" 36#include "AccessibilityARIAGridCell.h" 37#include "AccessibilityARIAGridRow.h" 38#include "AccessibilityImageMapLink.h" 39#include "AccessibilityList.h" 40#include "AccessibilityListBox.h" 41#include "AccessibilityListBoxOption.h" 42#include "AccessibilityMediaControls.h" 43#include "AccessibilityMenuList.h" 44#include "AccessibilityMenuListOption.h" 45#include "AccessibilityMenuListPopup.h" 46#include "AccessibilityProgressIndicator.h" 47#include "AccessibilityRenderObject.h" 48#include "AccessibilitySVGRoot.h" 49#include "AccessibilityScrollView.h" 50#include "AccessibilityScrollbar.h" 51#include "AccessibilitySearchFieldButtons.h" 52#include "AccessibilitySlider.h" 53#include "AccessibilitySpinButton.h" 54#include "AccessibilityTable.h" 55#include "AccessibilityTableCell.h" 56#include "AccessibilityTableColumn.h" 57#include "AccessibilityTableHeaderContainer.h" 58#include "AccessibilityTableRow.h" 59#include "Document.h" 60#include "Editor.h" 61#include "FocusController.h" 62#include "Frame.h" 63#include "HTMLAreaElement.h" 64#include "HTMLImageElement.h" 65#include "HTMLInputElement.h" 66#include "HTMLLabelElement.h" 67#include "HTMLMeterElement.h" 68#include "HTMLNames.h" 69#include "Page.h" 70#include "RenderListBox.h" 71#include "RenderMenuList.h" 72#include "RenderMeter.h" 73#include "RenderProgress.h" 74#include "RenderSlider.h" 75#include "RenderTable.h" 76#include "RenderTableCell.h" 77#include "RenderTableRow.h" 78#include "RenderView.h" 79#include "ScrollView.h" 80#include <wtf/PassRefPtr.h> 81 82#if ENABLE(VIDEO) 83#include "MediaControlElements.h" 84#endif 85 86namespace WebCore { 87 88using namespace HTMLNames; 89 90AccessibilityObjectInclusion AXComputedObjectAttributeCache::getIgnored(AXID id) const 91{ 92 HashMap<AXID, CachedAXObjectAttributes>::const_iterator it = m_idMapping.find(id); 93 return it != m_idMapping.end() ? it->value.ignored : DefaultBehavior; 94} 95 96void AXComputedObjectAttributeCache::setIgnored(AXID id, AccessibilityObjectInclusion inclusion) 97{ 98 HashMap<AXID, CachedAXObjectAttributes>::iterator it = m_idMapping.find(id); 99 if (it != m_idMapping.end()) 100 it->value.ignored = inclusion; 101 else { 102 CachedAXObjectAttributes attributes; 103 attributes.ignored = inclusion; 104 m_idMapping.set(id, attributes); 105 } 106} 107 108bool AXObjectCache::gAccessibilityEnabled = false; 109bool AXObjectCache::gAccessibilityEnhancedUserInterfaceEnabled = false; 110 111void AXObjectCache::enableAccessibility() 112{ 113 gAccessibilityEnabled = true; 114} 115 116void AXObjectCache::disableAccessibility() 117{ 118 gAccessibilityEnabled = false; 119} 120 121void AXObjectCache::setEnhancedUserInterfaceAccessibility(bool flag) 122{ 123 gAccessibilityEnhancedUserInterfaceEnabled = flag; 124} 125 126AXObjectCache::AXObjectCache(Document& document) 127 : m_document(document) 128 , m_notificationPostTimer(this, &AXObjectCache::notificationPostTimerFired) 129{ 130} 131 132AXObjectCache::~AXObjectCache() 133{ 134 m_notificationPostTimer.stop(); 135 136 for (const auto& object : m_objects.values()) { 137 detachWrapper(object.get(), CacheDestroyed); 138 object->detach(CacheDestroyed); 139 removeAXID(object.get()); 140 } 141} 142 143AccessibilityObject* AXObjectCache::focusedImageMapUIElement(HTMLAreaElement* areaElement) 144{ 145 // Find the corresponding accessibility object for the HTMLAreaElement. This should be 146 // in the list of children for its corresponding image. 147 if (!areaElement) 148 return 0; 149 150 HTMLImageElement* imageElement = areaElement->imageElement(); 151 if (!imageElement) 152 return 0; 153 154 AccessibilityObject* axRenderImage = areaElement->document().axObjectCache()->getOrCreate(imageElement); 155 if (!axRenderImage) 156 return 0; 157 158 for (const auto& child : axRenderImage->children()) { 159 if (!child->isImageMapLink()) 160 continue; 161 162 if (toAccessibilityImageMapLink(child.get())->areaElement() == areaElement) 163 return child.get(); 164 } 165 166 return 0; 167} 168 169AccessibilityObject* AXObjectCache::focusedUIElementForPage(const Page* page) 170{ 171 if (!gAccessibilityEnabled) 172 return 0; 173 174 // get the focused node in the page 175 Document* focusedDocument = page->focusController().focusedOrMainFrame().document(); 176 Element* focusedElement = focusedDocument->focusedElement(); 177 if (focusedElement && isHTMLAreaElement(focusedElement)) 178 return focusedImageMapUIElement(toHTMLAreaElement(focusedElement)); 179 180 AccessibilityObject* obj = focusedDocument->axObjectCache()->getOrCreate(focusedElement ? static_cast<Node*>(focusedElement) : focusedDocument); 181 if (!obj) 182 return 0; 183 184 if (obj->shouldFocusActiveDescendant()) { 185 if (AccessibilityObject* descendant = obj->activeDescendant()) 186 obj = descendant; 187 } 188 189 // the HTML element, for example, is focusable but has an AX object that is ignored 190 if (obj->accessibilityIsIgnored()) 191 obj = obj->parentObjectUnignored(); 192 193 return obj; 194} 195 196AccessibilityObject* AXObjectCache::get(Widget* widget) 197{ 198 if (!widget) 199 return 0; 200 201 AXID axID = m_widgetObjectMapping.get(widget); 202 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); 203 if (!axID) 204 return 0; 205 206 return m_objects.get(axID); 207} 208 209AccessibilityObject* AXObjectCache::get(RenderObject* renderer) 210{ 211 if (!renderer) 212 return 0; 213 214 AXID axID = m_renderObjectMapping.get(renderer); 215 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); 216 if (!axID) 217 return 0; 218 219 return m_objects.get(axID); 220} 221 222AccessibilityObject* AXObjectCache::get(Node* node) 223{ 224 if (!node) 225 return 0; 226 227 AXID renderID = node->renderer() ? m_renderObjectMapping.get(node->renderer()) : 0; 228 ASSERT(!HashTraits<AXID>::isDeletedValue(renderID)); 229 230 AXID nodeID = m_nodeObjectMapping.get(node); 231 ASSERT(!HashTraits<AXID>::isDeletedValue(nodeID)); 232 233 if (node->renderer() && nodeID && !renderID) { 234 // This can happen if an AccessibilityNodeObject is created for a node that's not 235 // rendered, but later something changes and it gets a renderer (like if it's 236 // reparented). 237 remove(nodeID); 238 return 0; 239 } 240 241 if (renderID) 242 return m_objects.get(renderID); 243 244 if (!nodeID) 245 return 0; 246 247 return m_objects.get(nodeID); 248} 249 250// FIXME: This probably belongs on Node. 251// FIXME: This should take a const char*, but one caller passes nullAtom. 252bool nodeHasRole(Node* node, const String& role) 253{ 254 if (!node || !node->isElementNode()) 255 return false; 256 257 auto& roleValue = toElement(node)->fastGetAttribute(roleAttr); 258 if (role.isNull()) 259 return roleValue.isEmpty(); 260 if (roleValue.isEmpty()) 261 return false; 262 263 return SpaceSplitString(roleValue, true).contains(role); 264} 265 266static PassRefPtr<AccessibilityObject> createFromRenderer(RenderObject* renderer) 267{ 268 // FIXME: How could renderer->node() ever not be an Element? 269 Node* node = renderer->node(); 270 271 // If the node is aria role="list" or the aria role is empty and its a 272 // ul/ol/dl type (it shouldn't be a list if aria says otherwise). 273 if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory")) 274 || (nodeHasRole(node, nullAtom) && (node->hasTagName(ulTag) || node->hasTagName(olTag) || node->hasTagName(dlTag))))) 275 return AccessibilityList::create(renderer); 276 277 // aria tables 278 if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid")) 279 return AccessibilityARIAGrid::create(renderer); 280 if (nodeHasRole(node, "row")) 281 return AccessibilityARIAGridRow::create(renderer); 282 if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || nodeHasRole(node, "rowheader")) 283 return AccessibilityARIAGridCell::create(renderer); 284 285#if ENABLE(VIDEO) 286 // media controls 287 if (node && node->isMediaControlElement()) 288 return AccessibilityMediaControl::create(renderer); 289#endif 290 291 if (renderer->isSVGRoot()) 292 return AccessibilitySVGRoot::create(renderer); 293 294 // Search field buttons 295 if (node && node->isElementNode() && toElement(node)->isSearchFieldCancelButtonElement()) 296 return AccessibilitySearchFieldCancelButton::create(renderer); 297 298 if (renderer->isBoxModelObject()) { 299 RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer); 300 if (cssBox->isListBox()) 301 return AccessibilityListBox::create(toRenderListBox(cssBox)); 302 if (cssBox->isMenuList()) 303 return AccessibilityMenuList::create(toRenderMenuList(cssBox)); 304 305 // standard tables 306 if (cssBox->isTable()) 307 return AccessibilityTable::create(toRenderTable(cssBox)); 308 if (cssBox->isTableRow()) 309 return AccessibilityTableRow::create(toRenderTableRow(cssBox)); 310 if (cssBox->isTableCell()) 311 return AccessibilityTableCell::create(toRenderTableCell(cssBox)); 312 313 // progress bar 314 if (cssBox->isProgress()) 315 return AccessibilityProgressIndicator::create(toRenderProgress(cssBox)); 316 317#if ENABLE(METER_ELEMENT) 318 if (cssBox->isMeter()) 319 return AccessibilityProgressIndicator::create(toRenderMeter(cssBox)); 320#endif 321 322 // input type=range 323 if (cssBox->isSlider()) 324 return AccessibilitySlider::create(toRenderSlider(cssBox)); 325 } 326 327 return AccessibilityRenderObject::create(renderer); 328} 329 330static PassRefPtr<AccessibilityObject> createFromNode(Node* node) 331{ 332 return AccessibilityNodeObject::create(node); 333} 334 335AccessibilityObject* AXObjectCache::getOrCreate(Widget* widget) 336{ 337 if (!widget) 338 return 0; 339 340 if (AccessibilityObject* obj = get(widget)) 341 return obj; 342 343 RefPtr<AccessibilityObject> newObj = 0; 344 if (widget->isFrameView()) 345 newObj = AccessibilityScrollView::create(toScrollView(widget)); 346 else if (widget->isScrollbar()) 347 newObj = AccessibilityScrollbar::create(toScrollbar(widget)); 348 349 // Will crash later if we have two objects for the same widget. 350 ASSERT(!get(widget)); 351 352 // Catch the case if an (unsupported) widget type is used. Only FrameView and ScrollBar are supported now. 353 ASSERT(newObj); 354 if (!newObj) 355 return 0; 356 357 getAXID(newObj.get()); 358 359 m_widgetObjectMapping.set(widget, newObj->axObjectID()); 360 m_objects.set(newObj->axObjectID(), newObj); 361 newObj->init(); 362 attachWrapper(newObj.get()); 363 return newObj.get(); 364} 365 366AccessibilityObject* AXObjectCache::getOrCreate(Node* node) 367{ 368 if (!node) 369 return 0; 370 371 if (AccessibilityObject* obj = get(node)) 372 return obj; 373 374 if (node->renderer()) 375 return getOrCreate(node->renderer()); 376 377 if (!node->parentElement()) 378 return 0; 379 380 // It's only allowed to create an AccessibilityObject from a Node if it's in a canvas subtree. 381 // Or if it's a hidden element, but we still want to expose it because of other ARIA attributes. 382 bool inCanvasSubtree = node->parentElement()->isInCanvasSubtree(); 383 bool isHidden = !node->renderer() && isNodeAriaVisible(node); 384 385 bool insideMeterElement = false; 386#if ENABLE(METER_ELEMENT) 387 insideMeterElement = isHTMLMeterElement(node->parentElement()); 388#endif 389 390 if (!inCanvasSubtree && !isHidden && !insideMeterElement) 391 return 0; 392 393 RefPtr<AccessibilityObject> newObj = createFromNode(node); 394 395 // Will crash later if we have two objects for the same node. 396 ASSERT(!get(node)); 397 398 getAXID(newObj.get()); 399 400 m_nodeObjectMapping.set(node, newObj->axObjectID()); 401 m_objects.set(newObj->axObjectID(), newObj); 402 newObj->init(); 403 attachWrapper(newObj.get()); 404 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); 405 // Sometimes asking accessibilityIsIgnored() will cause the newObject to be deallocated, and then 406 // it will disappear when this function is finished, leading to a use-after-free. 407 if (newObj->isDetached()) 408 return nullptr; 409 410 return newObj.get(); 411} 412 413AccessibilityObject* AXObjectCache::getOrCreate(RenderObject* renderer) 414{ 415 if (!renderer) 416 return 0; 417 418 if (AccessibilityObject* obj = get(renderer)) 419 return obj; 420 421 RefPtr<AccessibilityObject> newObj = createFromRenderer(renderer); 422 423 // Will crash later if we have two objects for the same renderer. 424 ASSERT(!get(renderer)); 425 426 getAXID(newObj.get()); 427 428 m_renderObjectMapping.set(renderer, newObj->axObjectID()); 429 m_objects.set(newObj->axObjectID(), newObj); 430 newObj->init(); 431 attachWrapper(newObj.get()); 432 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); 433 // Sometimes asking accessibilityIsIgnored() will cause the newObject to be deallocated, and then 434 // it will disappear when this function is finished, leading to a use-after-free. 435 if (newObj->isDetached()) 436 return nullptr; 437 438 return newObj.get(); 439} 440 441AccessibilityObject* AXObjectCache::rootObject() 442{ 443 if (!gAccessibilityEnabled) 444 return 0; 445 446 return getOrCreate(m_document.view()); 447} 448 449AccessibilityObject* AXObjectCache::rootObjectForFrame(Frame* frame) 450{ 451 if (!gAccessibilityEnabled) 452 return 0; 453 454 if (!frame) 455 return 0; 456 return getOrCreate(frame->view()); 457} 458 459AccessibilityObject* AXObjectCache::getOrCreate(AccessibilityRole role) 460{ 461 RefPtr<AccessibilityObject> obj = 0; 462 463 // will be filled in... 464 switch (role) { 465 case ListBoxOptionRole: 466 obj = AccessibilityListBoxOption::create(); 467 break; 468 case ImageMapLinkRole: 469 obj = AccessibilityImageMapLink::create(); 470 break; 471 case ColumnRole: 472 obj = AccessibilityTableColumn::create(); 473 break; 474 case TableHeaderContainerRole: 475 obj = AccessibilityTableHeaderContainer::create(); 476 break; 477 case SliderThumbRole: 478 obj = AccessibilitySliderThumb::create(); 479 break; 480 case MenuListPopupRole: 481 obj = AccessibilityMenuListPopup::create(); 482 break; 483 case MenuListOptionRole: 484 obj = AccessibilityMenuListOption::create(); 485 break; 486 case SpinButtonRole: 487 obj = AccessibilitySpinButton::create(); 488 break; 489 case SpinButtonPartRole: 490 obj = AccessibilitySpinButtonPart::create(); 491 break; 492 default: 493 obj = 0; 494 } 495 496 if (obj) 497 getAXID(obj.get()); 498 else 499 return 0; 500 501 m_objects.set(obj->axObjectID(), obj); 502 obj->init(); 503 attachWrapper(obj.get()); 504 return obj.get(); 505} 506 507void AXObjectCache::remove(AXID axID) 508{ 509 if (!axID) 510 return; 511 512 // first fetch object to operate some cleanup functions on it 513 AccessibilityObject* obj = m_objects.get(axID); 514 if (!obj) 515 return; 516 517 detachWrapper(obj, ElementDestroyed); 518 obj->detach(ElementDestroyed, this); 519 removeAXID(obj); 520 521 // finally remove the object 522 if (!m_objects.take(axID)) 523 return; 524 525 ASSERT(m_objects.size() >= m_idsInUse.size()); 526} 527 528void AXObjectCache::remove(RenderObject* renderer) 529{ 530 if (!renderer) 531 return; 532 533 AXID axID = m_renderObjectMapping.get(renderer); 534 remove(axID); 535 m_renderObjectMapping.remove(renderer); 536} 537 538void AXObjectCache::remove(Node* node) 539{ 540 if (!node) 541 return; 542 543 removeNodeForUse(node); 544 545 // This is all safe even if we didn't have a mapping. 546 AXID axID = m_nodeObjectMapping.get(node); 547 remove(axID); 548 m_nodeObjectMapping.remove(node); 549 550 if (node->renderer()) { 551 remove(node->renderer()); 552 return; 553 } 554} 555 556void AXObjectCache::remove(Widget* view) 557{ 558 if (!view) 559 return; 560 561 AXID axID = m_widgetObjectMapping.get(view); 562 remove(axID); 563 m_widgetObjectMapping.remove(view); 564} 565 566 567#if !PLATFORM(WIN) || OS(WINCE) 568AXID AXObjectCache::platformGenerateAXID() const 569{ 570 static AXID lastUsedID = 0; 571 572 // Generate a new ID. 573 AXID objID = lastUsedID; 574 do { 575 ++objID; 576 } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.contains(objID)); 577 578 lastUsedID = objID; 579 580 return objID; 581} 582#endif 583 584AXID AXObjectCache::getAXID(AccessibilityObject* obj) 585{ 586 // check for already-assigned ID 587 AXID objID = obj->axObjectID(); 588 if (objID) { 589 ASSERT(m_idsInUse.contains(objID)); 590 return objID; 591 } 592 593 objID = platformGenerateAXID(); 594 595 m_idsInUse.add(objID); 596 obj->setAXObjectID(objID); 597 598 return objID; 599} 600 601void AXObjectCache::removeAXID(AccessibilityObject* object) 602{ 603 if (!object) 604 return; 605 606 AXID objID = object->axObjectID(); 607 if (!objID) 608 return; 609 ASSERT(!HashTraits<AXID>::isDeletedValue(objID)); 610 ASSERT(m_idsInUse.contains(objID)); 611 object->setAXObjectID(0); 612 m_idsInUse.remove(objID); 613} 614 615void AXObjectCache::textChanged(Node* node) 616{ 617 textChanged(getOrCreate(node)); 618} 619 620void AXObjectCache::textChanged(RenderObject* renderer) 621{ 622 textChanged(getOrCreate(renderer)); 623} 624 625void AXObjectCache::textChanged(AccessibilityObject* obj) 626{ 627 if (!obj) 628 return; 629 630 bool parentAlreadyExists = obj->parentObjectIfExists(); 631 obj->textChanged(); 632 postNotification(obj, obj->document(), AXObjectCache::AXTextChanged); 633 if (parentAlreadyExists) 634 obj->notifyIfIgnoredValueChanged(); 635} 636 637void AXObjectCache::updateCacheAfterNodeIsAttached(Node* node) 638{ 639 // Calling get() will update the AX object if we had an AccessibilityNodeObject but now we need 640 // an AccessibilityRenderObject, because it was reparented to a location outside of a canvas. 641 get(node); 642} 643 644void AXObjectCache::handleMenuOpened(Node* node) 645{ 646 if (!node || !node->renderer() || !nodeHasRole(node, "menu")) 647 return; 648 649 postNotification(getOrCreate(node), &document(), AXMenuOpened); 650} 651 652void AXObjectCache::handleLiveRegionCreated(Node* node) 653{ 654 if (!node || !node->renderer() || !node->isElementNode()) 655 return; 656 657 Element* element = toElement(node); 658 String liveRegionStatus = element->fastGetAttribute(aria_liveAttr); 659 if (liveRegionStatus.isEmpty()) { 660 const AtomicString& ariaRole = element->fastGetAttribute(roleAttr); 661 if (!ariaRole.isEmpty()) 662 liveRegionStatus = AccessibilityObject::defaultLiveRegionStatusForRole(AccessibilityObject::ariaRoleToWebCoreRole(ariaRole)); 663 } 664 665 if (AccessibilityObject::liveRegionStatusIsEnabled(liveRegionStatus)) 666 postNotification(getOrCreate(node), &document(), AXLiveRegionCreated); 667} 668 669void AXObjectCache::childrenChanged(Node* node, Node* newChild) 670{ 671 if (newChild) { 672 handleMenuOpened(newChild); 673 handleLiveRegionCreated(newChild); 674 } 675 676 childrenChanged(get(node)); 677} 678 679void AXObjectCache::childrenChanged(RenderObject* renderer, RenderObject* newChild) 680{ 681 if (!renderer) 682 return; 683 684 if (newChild) { 685 handleMenuOpened(newChild->node()); 686 handleLiveRegionCreated(newChild->node()); 687 } 688 689 childrenChanged(get(renderer)); 690} 691 692void AXObjectCache::childrenChanged(AccessibilityObject* obj) 693{ 694 if (!obj) 695 return; 696 697 obj->childrenChanged(); 698} 699 700void AXObjectCache::notificationPostTimerFired(Timer<AXObjectCache>&) 701{ 702 Ref<Document> protectorForCacheOwner(m_document); 703 m_notificationPostTimer.stop(); 704 705 // In DRT, posting notifications has a tendency to immediately queue up other notifications, which can lead to unexpected behavior 706 // when the notification list is cleared at the end. Instead copy this list at the start. 707 auto notifications = WTF::move(m_notificationsToPost); 708 709 for (const auto& note : notifications) { 710 AccessibilityObject* obj = note.first.get(); 711 if (!obj->axObjectID()) 712 continue; 713 714 if (!obj->axObjectCache()) 715 continue; 716 717#ifndef NDEBUG 718 // Make sure none of the render views are in the process of being layed out. 719 // Notifications should only be sent after the renderer has finished 720 if (obj->isAccessibilityRenderObject()) { 721 AccessibilityRenderObject* renderObj = toAccessibilityRenderObject(obj); 722 RenderObject* renderer = renderObj->renderer(); 723 if (renderer) 724 ASSERT(!renderer->view().layoutState()); 725 } 726#endif 727 728 AXNotification notification = note.second; 729 730 // Ensure that this menu really is a menu. We do this check here so that we don't have to create 731 // the axChildren when the menu is marked as opening. 732 if (notification == AXMenuOpened) { 733 obj->updateChildrenIfNecessary(); 734 if (obj->roleValue() != MenuRole) 735 continue; 736 } 737 738 postPlatformNotification(obj, notification); 739 740 if (notification == AXChildrenChanged && obj->parentObjectIfExists() && obj->lastKnownIsIgnoredValue() != obj->accessibilityIsIgnored()) 741 childrenChanged(obj->parentObject()); 742 } 743} 744 745void AXObjectCache::postNotification(RenderObject* renderer, AXNotification notification, PostTarget postTarget, PostType postType) 746{ 747 if (!renderer) 748 return; 749 750 stopCachingComputedObjectAttributes(); 751 752 // Get an accessibility object that already exists. One should not be created here 753 // because a render update may be in progress and creating an AX object can re-trigger a layout 754 RefPtr<AccessibilityObject> object = get(renderer); 755 while (!object && renderer) { 756 renderer = renderer->parent(); 757 object = get(renderer); 758 } 759 760 if (!renderer) 761 return; 762 763 postNotification(object.get(), &renderer->document(), notification, postTarget, postType); 764} 765 766void AXObjectCache::postNotification(Node* node, AXNotification notification, PostTarget postTarget, PostType postType) 767{ 768 if (!node) 769 return; 770 771 stopCachingComputedObjectAttributes(); 772 773 // Get an accessibility object that already exists. One should not be created here 774 // because a render update may be in progress and creating an AX object can re-trigger a layout 775 RefPtr<AccessibilityObject> object = get(node); 776 while (!object && node) { 777 node = node->parentNode(); 778 object = get(node); 779 } 780 781 if (!node) 782 return; 783 784 postNotification(object.get(), &node->document(), notification, postTarget, postType); 785} 786 787void AXObjectCache::postNotification(AccessibilityObject* object, Document* document, AXNotification notification, PostTarget postTarget, PostType postType) 788{ 789 stopCachingComputedObjectAttributes(); 790 791 if (object && postTarget == TargetObservableParent) 792 object = object->observableObject(); 793 794 if (!object && document) 795 object = get(document->renderView()); 796 797 if (!object) 798 return; 799 800 if (postType == PostAsynchronously) { 801 m_notificationsToPost.append(std::make_pair(object, notification)); 802 if (!m_notificationPostTimer.isActive()) 803 m_notificationPostTimer.startOneShot(0); 804 } else 805 postPlatformNotification(object, notification); 806} 807 808void AXObjectCache::checkedStateChanged(Node* node) 809{ 810 postNotification(node, AXObjectCache::AXCheckedStateChanged); 811} 812 813void AXObjectCache::handleMenuItemSelected(Node* node) 814{ 815 if (!node) 816 return; 817 818 if (!nodeHasRole(node, "menuitem") && !nodeHasRole(node, "menuitemradio") && !nodeHasRole(node, "menuitemcheckbox")) 819 return; 820 821 if (!toElement(node)->focused() && !equalIgnoringCase(toElement(node)->fastGetAttribute(aria_selectedAttr), "true")) 822 return; 823 824 postNotification(getOrCreate(node), &document(), AXMenuListItemSelected); 825} 826 827void AXObjectCache::handleFocusedUIElementChanged(Node* oldNode, Node* newNode) 828{ 829 handleMenuItemSelected(newNode); 830 platformHandleFocusedUIElementChanged(oldNode, newNode); 831} 832 833void AXObjectCache::selectedChildrenChanged(Node* node) 834{ 835 handleMenuItemSelected(node); 836 837 // postTarget is TargetObservableParent so that you can pass in any child of an element and it will go up the parent tree 838 // to find the container which should send out the notification. 839 postNotification(node, AXSelectedChildrenChanged, TargetObservableParent); 840} 841 842void AXObjectCache::selectedChildrenChanged(RenderObject* renderer) 843{ 844 if (renderer) 845 handleMenuItemSelected(renderer->node()); 846 847 // postTarget is TargetObservableParent so that you can pass in any child of an element and it will go up the parent tree 848 // to find the container which should send out the notification. 849 postNotification(renderer, AXSelectedChildrenChanged, TargetObservableParent); 850} 851 852void AXObjectCache::nodeTextChangeNotification(Node* node, AXTextChange textChange, unsigned offset, const String& text) 853{ 854 if (!node) 855 return; 856 857 stopCachingComputedObjectAttributes(); 858 859 // Delegate on the right platform 860 AccessibilityObject* obj = getOrCreate(node); 861 nodeTextChangePlatformNotification(obj, textChange, offset, text); 862} 863 864void AXObjectCache::frameLoadingEventNotification(Frame* frame, AXLoadingEvent loadingEvent) 865{ 866 if (!frame) 867 return; 868 869 // Delegate on the right platform 870 RenderView* contentRenderer = frame->contentRenderer(); 871 if (!contentRenderer) 872 return; 873 874 AccessibilityObject* obj = getOrCreate(contentRenderer); 875 frameLoadingEventPlatformNotification(obj, loadingEvent); 876} 877 878void AXObjectCache::handleScrollbarUpdate(ScrollView* view) 879{ 880 if (!view) 881 return; 882 883 // We don't want to create a scroll view from this method, only update an existing one. 884 if (AccessibilityObject* scrollViewObject = get(view)) { 885 stopCachingComputedObjectAttributes(); 886 scrollViewObject->updateChildrenIfNecessary(); 887 } 888} 889 890void AXObjectCache::handleAriaExpandedChange(Node* node) 891{ 892 if (AccessibilityObject* obj = getOrCreate(node)) 893 obj->handleAriaExpandedChanged(); 894} 895 896void AXObjectCache::handleActiveDescendantChanged(Node* node) 897{ 898 if (AccessibilityObject* obj = getOrCreate(node)) 899 obj->handleActiveDescendantChanged(); 900} 901 902void AXObjectCache::handleAriaRoleChanged(Node* node) 903{ 904 stopCachingComputedObjectAttributes(); 905 906 if (AccessibilityObject* obj = getOrCreate(node)) { 907 obj->updateAccessibilityRole(); 908 obj->notifyIfIgnoredValueChanged(); 909 } 910} 911 912void AXObjectCache::handleAttributeChanged(const QualifiedName& attrName, Element* element) 913{ 914 if (attrName == roleAttr) 915 handleAriaRoleChanged(element); 916 else if (attrName == altAttr || attrName == titleAttr) 917 textChanged(element); 918 else if (attrName == forAttr && isHTMLLabelElement(element)) 919 labelChanged(element); 920 921 if (!attrName.localName().string().startsWith("aria-")) 922 return; 923 924 if (attrName == aria_activedescendantAttr) 925 handleActiveDescendantChanged(element); 926 else if (attrName == aria_busyAttr) 927 postNotification(element, AXObjectCache::AXElementBusyChanged); 928 else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr) 929 postNotification(element, AXObjectCache::AXValueChanged); 930 else if (attrName == aria_labelAttr || attrName == aria_labeledbyAttr || attrName == aria_labelledbyAttr) 931 textChanged(element); 932 else if (attrName == aria_checkedAttr) 933 checkedStateChanged(element); 934 else if (attrName == aria_selectedAttr) 935 selectedChildrenChanged(element); 936 else if (attrName == aria_expandedAttr) 937 handleAriaExpandedChange(element); 938 else if (attrName == aria_hiddenAttr) 939 childrenChanged(element->parentNode(), element); 940 else if (attrName == aria_invalidAttr) 941 postNotification(element, AXObjectCache::AXInvalidStatusChanged); 942 else 943 postNotification(element, AXObjectCache::AXAriaAttributeChanged); 944} 945 946void AXObjectCache::labelChanged(Element* element) 947{ 948 ASSERT(isHTMLLabelElement(element)); 949 HTMLElement* correspondingControl = toHTMLLabelElement(element)->control(); 950 textChanged(correspondingControl); 951} 952 953void AXObjectCache::recomputeIsIgnored(RenderObject* renderer) 954{ 955 if (AccessibilityObject* obj = get(renderer)) 956 obj->notifyIfIgnoredValueChanged(); 957} 958 959void AXObjectCache::startCachingComputedObjectAttributesUntilTreeMutates() 960{ 961 if (!m_computedObjectAttributeCache) 962 m_computedObjectAttributeCache = std::make_unique<AXComputedObjectAttributeCache>(); 963} 964 965void AXObjectCache::stopCachingComputedObjectAttributes() 966{ 967 m_computedObjectAttributeCache = nullptr; 968} 969 970VisiblePosition AXObjectCache::visiblePositionForTextMarkerData(TextMarkerData& textMarkerData) 971{ 972 if (!isNodeInUse(textMarkerData.node)) 973 return VisiblePosition(); 974 975 // FIXME: Accessability should make it clear these are DOM-compliant offsets or store Position objects. 976 VisiblePosition visiblePos = VisiblePosition(createLegacyEditingPosition(textMarkerData.node, textMarkerData.offset), textMarkerData.affinity); 977 Position deepPos = visiblePos.deepEquivalent(); 978 if (deepPos.isNull()) 979 return VisiblePosition(); 980 981 RenderObject* renderer = deepPos.deprecatedNode()->renderer(); 982 if (!renderer) 983 return VisiblePosition(); 984 985 AXObjectCache* cache = renderer->document().axObjectCache(); 986 if (!cache->isIDinUse(textMarkerData.axID)) 987 return VisiblePosition(); 988 989 if (deepPos.deprecatedNode() != textMarkerData.node || deepPos.deprecatedEditingOffset() != textMarkerData.offset) 990 return VisiblePosition(); 991 992 return visiblePos; 993} 994 995void AXObjectCache::textMarkerDataForVisiblePosition(TextMarkerData& textMarkerData, const VisiblePosition& visiblePos) 996{ 997 // This memory must be bzero'd so instances of TextMarkerData can be tested for byte-equivalence. 998 // This also allows callers to check for failure by looking at textMarkerData upon return. 999 memset(&textMarkerData, 0, sizeof(TextMarkerData)); 1000 1001 if (visiblePos.isNull()) 1002 return; 1003 1004 Position deepPos = visiblePos.deepEquivalent(); 1005 Node* domNode = deepPos.deprecatedNode(); 1006 ASSERT(domNode); 1007 if (!domNode) 1008 return; 1009 1010 if (domNode->isHTMLElement()) { 1011 HTMLInputElement* inputElement = domNode->toInputElement(); 1012 if (inputElement && inputElement->isPasswordField()) 1013 return; 1014 } 1015 1016 // find or create an accessibility object for this node 1017 AXObjectCache* cache = domNode->document().axObjectCache(); 1018 RefPtr<AccessibilityObject> obj = cache->getOrCreate(domNode); 1019 1020 textMarkerData.axID = obj.get()->axObjectID(); 1021 textMarkerData.node = domNode; 1022 textMarkerData.offset = deepPos.deprecatedEditingOffset(); 1023 textMarkerData.affinity = visiblePos.affinity(); 1024 1025 cache->setNodeInUse(domNode); 1026} 1027 1028const Element* AXObjectCache::rootAXEditableElement(const Node* node) 1029{ 1030 const Element* result = node->rootEditableElement(); 1031 const Element* element = node->isElementNode() ? toElement(node) : node->parentElement(); 1032 1033 for (; element; element = element->parentElement()) { 1034 if (nodeIsTextControl(element)) 1035 result = element; 1036 } 1037 1038 return result; 1039} 1040 1041void AXObjectCache::clearTextMarkerNodesInUse(Document* document) 1042{ 1043 if (!document) 1044 return; 1045 1046 // Check each node to see if it's inside the document being deleted, of if it no longer belongs to a document. 1047 HashSet<Node*> nodesToDelete; 1048 for (const auto& node : m_textMarkerNodes) { 1049 if (!node->inDocument() || &(node)->document() == document) 1050 nodesToDelete.add(node); 1051 } 1052 1053 for (const auto& node : nodesToDelete) 1054 m_textMarkerNodes.remove(node); 1055} 1056 1057bool AXObjectCache::nodeIsTextControl(const Node* node) 1058{ 1059 if (!node) 1060 return false; 1061 1062 const AccessibilityObject* axObject = getOrCreate(const_cast<Node*>(node)); 1063 return axObject && axObject->isTextControl(); 1064} 1065 1066bool isNodeAriaVisible(Node* node) 1067{ 1068 if (!node) 1069 return false; 1070 1071 // To determine if a node is ARIA visible, we need to check the parent hierarchy to see if anyone specifies 1072 // aria-hidden explicitly. 1073 for (Node* testNode = node; testNode; testNode = testNode->parentNode()) { 1074 if (testNode->isElementNode()) { 1075 const AtomicString& ariaHiddenValue = toElement(testNode)->fastGetAttribute(aria_hiddenAttr); 1076 if (equalIgnoringCase(ariaHiddenValue, "false")) 1077 return true; 1078 if (equalIgnoringCase(ariaHiddenValue, "true")) 1079 return false; 1080 } 1081 } 1082 1083 return false; 1084} 1085 1086AXAttributeCacheEnabler::AXAttributeCacheEnabler(AXObjectCache* cache) 1087 : m_cache(cache) 1088{ 1089 if (m_cache) 1090 m_cache->startCachingComputedObjectAttributesUntilTreeMutates(); 1091} 1092 1093AXAttributeCacheEnabler::~AXAttributeCacheEnabler() 1094{ 1095 if (m_cache) 1096 m_cache->stopCachingComputedObjectAttributes(); 1097} 1098 1099} // namespace WebCore 1100 1101#endif // HAVE(ACCESSIBILITY) 1102