| OLD | NEW |
| (Empty) |
| 1 /* | |
| 2 * Copyright (C) 2014, Google 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 Computer, 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 #include "core/accessibility/AXObjectCacheImpl.h" | |
| 32 | |
| 33 #include "core/HTMLNames.h" | |
| 34 #include "core/accessibility/AXARIAGrid.h" | |
| 35 #include "core/accessibility/AXARIAGridCell.h" | |
| 36 #include "core/accessibility/AXARIAGridRow.h" | |
| 37 #include "core/accessibility/AXImageMapLink.h" | |
| 38 #include "core/accessibility/AXInlineTextBox.h" | |
| 39 #include "core/accessibility/AXList.h" | |
| 40 #include "core/accessibility/AXListBox.h" | |
| 41 #include "core/accessibility/AXListBoxOption.h" | |
| 42 #include "core/accessibility/AXMediaControls.h" | |
| 43 #include "core/accessibility/AXMenuList.h" | |
| 44 #include "core/accessibility/AXMenuListOption.h" | |
| 45 #include "core/accessibility/AXMenuListPopup.h" | |
| 46 #include "core/accessibility/AXProgressIndicator.h" | |
| 47 #include "core/accessibility/AXRenderObject.h" | |
| 48 #include "core/accessibility/AXSVGRoot.h" | |
| 49 #include "core/accessibility/AXScrollView.h" | |
| 50 #include "core/accessibility/AXScrollbar.h" | |
| 51 #include "core/accessibility/AXSlider.h" | |
| 52 #include "core/accessibility/AXSpinButton.h" | |
| 53 #include "core/accessibility/AXTable.h" | |
| 54 #include "core/accessibility/AXTableCell.h" | |
| 55 #include "core/accessibility/AXTableColumn.h" | |
| 56 #include "core/accessibility/AXTableHeaderContainer.h" | |
| 57 #include "core/accessibility/AXTableRow.h" | |
| 58 #include "core/dom/Document.h" | |
| 59 #include "core/frame/FrameView.h" | |
| 60 #include "core/frame/LocalFrame.h" | |
| 61 #include "core/frame/Settings.h" | |
| 62 #include "core/html/HTMLAreaElement.h" | |
| 63 #include "core/html/HTMLImageElement.h" | |
| 64 #include "core/html/HTMLInputElement.h" | |
| 65 #include "core/html/HTMLLabelElement.h" | |
| 66 #include "core/page/Chrome.h" | |
| 67 #include "core/page/ChromeClient.h" | |
| 68 #include "core/page/FocusController.h" | |
| 69 #include "core/page/Page.h" | |
| 70 #include "core/rendering/AbstractInlineTextBox.h" | |
| 71 #include "core/rendering/RenderListBox.h" | |
| 72 #include "core/rendering/RenderMenuList.h" | |
| 73 #include "core/rendering/RenderProgress.h" | |
| 74 #include "core/rendering/RenderSlider.h" | |
| 75 #include "core/rendering/RenderTable.h" | |
| 76 #include "core/rendering/RenderTableCell.h" | |
| 77 #include "core/rendering/RenderTableRow.h" | |
| 78 #include "core/rendering/RenderView.h" | |
| 79 #include "wtf/PassRefPtr.h" | |
| 80 | |
| 81 namespace blink { | |
| 82 | |
| 83 using namespace HTMLNames; | |
| 84 | |
| 85 // static | |
| 86 AXObjectCache* AXObjectCache::create(Document& document) | |
| 87 { | |
| 88 return new AXObjectCacheImpl(document); | |
| 89 } | |
| 90 | |
| 91 AXObjectCacheImpl::AXObjectCacheImpl(Document& document) | |
| 92 : m_document(document) | |
| 93 , m_modificationCount(0) | |
| 94 , m_notificationPostTimer(this, &AXObjectCacheImpl::notificationPostTimerFir
ed) | |
| 95 { | |
| 96 } | |
| 97 | |
| 98 AXObjectCacheImpl::~AXObjectCacheImpl() | |
| 99 { | |
| 100 m_notificationPostTimer.stop(); | |
| 101 | |
| 102 HashMap<AXID, RefPtr<AXObject> >::iterator end = m_objects.end(); | |
| 103 for (HashMap<AXID, RefPtr<AXObject> >::iterator it = m_objects.begin(); it !
= end; ++it) { | |
| 104 AXObject* obj = (*it).value.get(); | |
| 105 detachWrapper(obj); | |
| 106 obj->detach(); | |
| 107 removeAXID(obj); | |
| 108 } | |
| 109 } | |
| 110 | |
| 111 AXObject* AXObjectCacheImpl::getOrCreateAXObjectFromRenderView(RenderView* rende
rView) | |
| 112 { | |
| 113 return getOrCreate(renderView); | |
| 114 } | |
| 115 | |
| 116 AXObject* AXObjectCacheImpl::root() | |
| 117 { | |
| 118 return getOrCreate(&m_document); | |
| 119 } | |
| 120 | |
| 121 AXObject* AXObjectCacheImpl::focusedImageMapUIElement(HTMLAreaElement* areaEleme
nt) | |
| 122 { | |
| 123 // Find the corresponding accessibility object for the HTMLAreaElement. This
should be | |
| 124 // in the list of children for its corresponding image. | |
| 125 if (!areaElement) | |
| 126 return 0; | |
| 127 | |
| 128 HTMLImageElement* imageElement = areaElement->imageElement(); | |
| 129 if (!imageElement) | |
| 130 return 0; | |
| 131 | |
| 132 AXObject* axRenderImage = toAXObjectCacheImpl(areaElement->document().axObje
ctCache())->getOrCreate(imageElement); | |
| 133 if (!axRenderImage) | |
| 134 return 0; | |
| 135 | |
| 136 AXObject::AccessibilityChildrenVector imageChildren = axRenderImage->childre
n(); | |
| 137 unsigned count = imageChildren.size(); | |
| 138 for (unsigned k = 0; k < count; ++k) { | |
| 139 AXObject* child = imageChildren[k].get(); | |
| 140 if (!child->isImageMapLink()) | |
| 141 continue; | |
| 142 | |
| 143 if (toAXImageMapLink(child)->areaElement() == areaElement) | |
| 144 return child; | |
| 145 } | |
| 146 | |
| 147 return 0; | |
| 148 } | |
| 149 | |
| 150 AXObject* AXObjectCacheImpl::focusedUIElementForPage(const Page* page) | |
| 151 { | |
| 152 if (!page->settings().accessibilityEnabled()) | |
| 153 return 0; | |
| 154 | |
| 155 // Cross-process accessibility is not yet implemented. | |
| 156 if (!page->focusController().focusedOrMainFrame()->isLocalFrame()) | |
| 157 return 0; | |
| 158 | |
| 159 // get the focused node in the page | |
| 160 Document* focusedDocument = toLocalFrame(page->focusController().focusedOrMa
inFrame())->document(); | |
| 161 Node* focusedNode = focusedDocument->focusedElement(); | |
| 162 if (!focusedNode) | |
| 163 focusedNode = focusedDocument; | |
| 164 | |
| 165 if (isHTMLAreaElement(*focusedNode)) | |
| 166 return focusedImageMapUIElement(toHTMLAreaElement(focusedNode)); | |
| 167 | |
| 168 AXObject* obj = toAXObjectCacheImpl(focusedNode->document().axObjectCache())
->getOrCreate(focusedNode); | |
| 169 if (!obj) | |
| 170 return 0; | |
| 171 | |
| 172 if (obj->shouldFocusActiveDescendant()) { | |
| 173 if (AXObject* descendant = obj->activeDescendant()) | |
| 174 obj = descendant; | |
| 175 } | |
| 176 | |
| 177 // the HTML element, for example, is focusable but has an AX object that is
ignored | |
| 178 if (obj->accessibilityIsIgnored()) | |
| 179 obj = obj->parentObjectUnignored(); | |
| 180 | |
| 181 return obj; | |
| 182 } | |
| 183 | |
| 184 AXObject* AXObjectCacheImpl::get(Widget* widget) | |
| 185 { | |
| 186 if (!widget) | |
| 187 return 0; | |
| 188 | |
| 189 AXID axID = m_widgetObjectMapping.get(widget); | |
| 190 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); | |
| 191 if (!axID) | |
| 192 return 0; | |
| 193 | |
| 194 return m_objects.get(axID); | |
| 195 } | |
| 196 | |
| 197 AXObject* AXObjectCacheImpl::get(RenderObject* renderer) | |
| 198 { | |
| 199 if (!renderer) | |
| 200 return 0; | |
| 201 | |
| 202 AXID axID = m_renderObjectMapping.get(renderer); | |
| 203 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); | |
| 204 if (!axID) | |
| 205 return 0; | |
| 206 | |
| 207 return m_objects.get(axID); | |
| 208 } | |
| 209 | |
| 210 AXObject* AXObjectCacheImpl::get(Node* node) | |
| 211 { | |
| 212 if (!node) | |
| 213 return 0; | |
| 214 | |
| 215 AXID renderID = node->renderer() ? m_renderObjectMapping.get(node->renderer(
)) : 0; | |
| 216 ASSERT(!HashTraits<AXID>::isDeletedValue(renderID)); | |
| 217 | |
| 218 AXID nodeID = m_nodeObjectMapping.get(node); | |
| 219 ASSERT(!HashTraits<AXID>::isDeletedValue(nodeID)); | |
| 220 | |
| 221 if (node->renderer() && nodeID && !renderID) { | |
| 222 // This can happen if an AXNodeObject is created for a node that's not | |
| 223 // rendered, but later something changes and it gets a renderer (like if
it's | |
| 224 // reparented). | |
| 225 remove(nodeID); | |
| 226 return 0; | |
| 227 } | |
| 228 | |
| 229 if (renderID) | |
| 230 return m_objects.get(renderID); | |
| 231 | |
| 232 if (!nodeID) | |
| 233 return 0; | |
| 234 | |
| 235 return m_objects.get(nodeID); | |
| 236 } | |
| 237 | |
| 238 AXObject* AXObjectCacheImpl::get(AbstractInlineTextBox* inlineTextBox) | |
| 239 { | |
| 240 if (!inlineTextBox) | |
| 241 return 0; | |
| 242 | |
| 243 AXID axID = m_inlineTextBoxObjectMapping.get(inlineTextBox); | |
| 244 ASSERT(!HashTraits<AXID>::isDeletedValue(axID)); | |
| 245 if (!axID) | |
| 246 return 0; | |
| 247 | |
| 248 return m_objects.get(axID); | |
| 249 } | |
| 250 | |
| 251 // FIXME: This probably belongs on Node. | |
| 252 // FIXME: This should take a const char*, but one caller passes nullAtom. | |
| 253 bool nodeHasRole(Node* node, const String& role) | |
| 254 { | |
| 255 if (!node || !node->isElementNode()) | |
| 256 return false; | |
| 257 | |
| 258 return equalIgnoringCase(toElement(node)->getAttribute(roleAttr), role); | |
| 259 } | |
| 260 | |
| 261 static PassRefPtr<AXObject> createFromRenderer(RenderObject* renderer) | |
| 262 { | |
| 263 // FIXME: How could renderer->node() ever not be an Element? | |
| 264 Node* node = renderer->node(); | |
| 265 | |
| 266 // If the node is aria role="list" or the aria role is empty and its a | |
| 267 // ul/ol/dl type (it shouldn't be a list if aria says otherwise). | |
| 268 if (node && ((nodeHasRole(node, "list") || nodeHasRole(node, "directory")) | |
| 269 || (nodeHasRole(node, nullAtom) && (isHTMLUListElement(*node) || isHTMLO
ListElement(*node) || isHTMLDListElement(*node))))) | |
| 270 return AXList::create(renderer); | |
| 271 | |
| 272 // aria tables | |
| 273 if (nodeHasRole(node, "grid") || nodeHasRole(node, "treegrid")) | |
| 274 return AXARIAGrid::create(renderer); | |
| 275 if (nodeHasRole(node, "row")) | |
| 276 return AXARIAGridRow::create(renderer); | |
| 277 if (nodeHasRole(node, "gridcell") || nodeHasRole(node, "columnheader") || no
deHasRole(node, "rowheader")) | |
| 278 return AXARIAGridCell::create(renderer); | |
| 279 | |
| 280 // media controls | |
| 281 if (node && node->isMediaControlElement()) | |
| 282 return AccessibilityMediaControl::create(renderer); | |
| 283 | |
| 284 if (isHTMLOptionElement(node)) | |
| 285 return AXListBoxOption::create(renderer); | |
| 286 | |
| 287 if (renderer->isSVGRoot()) | |
| 288 return AXSVGRoot::create(renderer); | |
| 289 | |
| 290 if (renderer->isBoxModelObject()) { | |
| 291 RenderBoxModelObject* cssBox = toRenderBoxModelObject(renderer); | |
| 292 if (cssBox->isListBox()) | |
| 293 return AXListBox::create(toRenderListBox(cssBox)); | |
| 294 if (cssBox->isMenuList()) | |
| 295 return AXMenuList::create(toRenderMenuList(cssBox)); | |
| 296 | |
| 297 // standard tables | |
| 298 if (cssBox->isTable()) | |
| 299 return AXTable::create(toRenderTable(cssBox)); | |
| 300 if (cssBox->isTableRow()) | |
| 301 return AXTableRow::create(toRenderTableRow(cssBox)); | |
| 302 if (cssBox->isTableCell()) | |
| 303 return AXTableCell::create(toRenderTableCell(cssBox)); | |
| 304 | |
| 305 // progress bar | |
| 306 if (cssBox->isProgress()) | |
| 307 return AXProgressIndicator::create(toRenderProgress(cssBox)); | |
| 308 | |
| 309 // input type=range | |
| 310 if (cssBox->isSlider()) | |
| 311 return AXSlider::create(toRenderSlider(cssBox)); | |
| 312 } | |
| 313 | |
| 314 return AXRenderObject::create(renderer); | |
| 315 } | |
| 316 | |
| 317 static PassRefPtr<AXObject> createFromNode(Node* node) | |
| 318 { | |
| 319 return AXNodeObject::create(node); | |
| 320 } | |
| 321 | |
| 322 static PassRefPtr<AXObject> createFromInlineTextBox(AbstractInlineTextBox* inlin
eTextBox) | |
| 323 { | |
| 324 return AXInlineTextBox::create(inlineTextBox); | |
| 325 } | |
| 326 | |
| 327 AXObject* AXObjectCacheImpl::getOrCreate(Widget* widget) | |
| 328 { | |
| 329 if (!widget) | |
| 330 return 0; | |
| 331 | |
| 332 if (AXObject* obj = get(widget)) | |
| 333 return obj; | |
| 334 | |
| 335 RefPtr<AXObject> newObj = nullptr; | |
| 336 if (widget->isFrameView()) | |
| 337 newObj = AXScrollView::create(toFrameView(widget)); | |
| 338 else if (widget->isScrollbar()) | |
| 339 newObj = AXScrollbar::create(toScrollbar(widget)); | |
| 340 | |
| 341 // Will crash later if we have two objects for the same widget. | |
| 342 ASSERT(!get(widget)); | |
| 343 | |
| 344 // Catch the case if an (unsupported) widget type is used. Only FrameView an
d ScrollBar are supported now. | |
| 345 ASSERT(newObj); | |
| 346 if (!newObj) | |
| 347 return 0; | |
| 348 | |
| 349 getAXID(newObj.get()); | |
| 350 | |
| 351 m_widgetObjectMapping.set(widget, newObj->axObjectID()); | |
| 352 m_objects.set(newObj->axObjectID(), newObj); | |
| 353 newObj->init(); | |
| 354 attachWrapper(newObj.get()); | |
| 355 return newObj.get(); | |
| 356 } | |
| 357 | |
| 358 AXObject* AXObjectCacheImpl::getOrCreate(Node* node) | |
| 359 { | |
| 360 if (!node) | |
| 361 return 0; | |
| 362 | |
| 363 if (AXObject* obj = get(node)) | |
| 364 return obj; | |
| 365 | |
| 366 if (node->renderer()) | |
| 367 return getOrCreate(node->renderer()); | |
| 368 | |
| 369 if (!node->parentElement()) | |
| 370 return 0; | |
| 371 | |
| 372 // It's only allowed to create an AXObject from a Node if it's in a canvas s
ubtree. | |
| 373 // Or if it's a hidden element, but we still want to expose it because of ot
her ARIA attributes. | |
| 374 bool inCanvasSubtree = node->parentElement()->isInCanvasSubtree(); | |
| 375 bool isHidden = !node->renderer() && isNodeAriaVisible(node); | |
| 376 if (!inCanvasSubtree && !isHidden) | |
| 377 return 0; | |
| 378 | |
| 379 RefPtr<AXObject> newObj = createFromNode(node); | |
| 380 | |
| 381 // Will crash later if we have two objects for the same node. | |
| 382 ASSERT(!get(node)); | |
| 383 | |
| 384 getAXID(newObj.get()); | |
| 385 | |
| 386 m_nodeObjectMapping.set(node, newObj->axObjectID()); | |
| 387 m_objects.set(newObj->axObjectID(), newObj); | |
| 388 newObj->init(); | |
| 389 attachWrapper(newObj.get()); | |
| 390 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); | |
| 391 | |
| 392 return newObj.get(); | |
| 393 } | |
| 394 | |
| 395 AXObject* AXObjectCacheImpl::getOrCreate(RenderObject* renderer) | |
| 396 { | |
| 397 if (!renderer) | |
| 398 return 0; | |
| 399 | |
| 400 if (AXObject* obj = get(renderer)) | |
| 401 return obj; | |
| 402 | |
| 403 RefPtr<AXObject> newObj = createFromRenderer(renderer); | |
| 404 | |
| 405 // Will crash later if we have two objects for the same renderer. | |
| 406 ASSERT(!get(renderer)); | |
| 407 | |
| 408 getAXID(newObj.get()); | |
| 409 | |
| 410 m_renderObjectMapping.set(renderer, newObj->axObjectID()); | |
| 411 m_objects.set(newObj->axObjectID(), newObj); | |
| 412 newObj->init(); | |
| 413 attachWrapper(newObj.get()); | |
| 414 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); | |
| 415 | |
| 416 return newObj.get(); | |
| 417 } | |
| 418 | |
| 419 AXObject* AXObjectCacheImpl::getOrCreate(AbstractInlineTextBox* inlineTextBox) | |
| 420 { | |
| 421 if (!inlineTextBox) | |
| 422 return 0; | |
| 423 | |
| 424 if (AXObject* obj = get(inlineTextBox)) | |
| 425 return obj; | |
| 426 | |
| 427 RefPtr<AXObject> newObj = createFromInlineTextBox(inlineTextBox); | |
| 428 | |
| 429 // Will crash later if we have two objects for the same inlineTextBox. | |
| 430 ASSERT(!get(inlineTextBox)); | |
| 431 | |
| 432 getAXID(newObj.get()); | |
| 433 | |
| 434 m_inlineTextBoxObjectMapping.set(inlineTextBox, newObj->axObjectID()); | |
| 435 m_objects.set(newObj->axObjectID(), newObj); | |
| 436 newObj->init(); | |
| 437 attachWrapper(newObj.get()); | |
| 438 newObj->setLastKnownIsIgnoredValue(newObj->accessibilityIsIgnored()); | |
| 439 | |
| 440 return newObj.get(); | |
| 441 } | |
| 442 | |
| 443 AXObject* AXObjectCacheImpl::rootObject() | |
| 444 { | |
| 445 if (!accessibilityEnabled()) | |
| 446 return 0; | |
| 447 | |
| 448 return getOrCreate(m_document.view()); | |
| 449 } | |
| 450 | |
| 451 AXObject* AXObjectCacheImpl::getOrCreate(AccessibilityRole role) | |
| 452 { | |
| 453 RefPtr<AXObject> obj = nullptr; | |
| 454 | |
| 455 // will be filled in... | |
| 456 switch (role) { | |
| 457 case ImageMapLinkRole: | |
| 458 obj = AXImageMapLink::create(); | |
| 459 break; | |
| 460 case ColumnRole: | |
| 461 obj = AXTableColumn::create(); | |
| 462 break; | |
| 463 case TableHeaderContainerRole: | |
| 464 obj = AXTableHeaderContainer::create(); | |
| 465 break; | |
| 466 case SliderThumbRole: | |
| 467 obj = AXSliderThumb::create(); | |
| 468 break; | |
| 469 case MenuListPopupRole: | |
| 470 obj = AXMenuListPopup::create(); | |
| 471 break; | |
| 472 case MenuListOptionRole: | |
| 473 obj = AXMenuListOption::create(); | |
| 474 break; | |
| 475 case SpinButtonRole: | |
| 476 obj = AXSpinButton::create(); | |
| 477 break; | |
| 478 case SpinButtonPartRole: | |
| 479 obj = AXSpinButtonPart::create(); | |
| 480 break; | |
| 481 default: | |
| 482 obj = nullptr; | |
| 483 } | |
| 484 | |
| 485 if (obj) | |
| 486 getAXID(obj.get()); | |
| 487 else | |
| 488 return 0; | |
| 489 | |
| 490 m_objects.set(obj->axObjectID(), obj); | |
| 491 obj->init(); | |
| 492 attachWrapper(obj.get()); | |
| 493 return obj.get(); | |
| 494 } | |
| 495 | |
| 496 void AXObjectCacheImpl::remove(AXID axID) | |
| 497 { | |
| 498 if (!axID) | |
| 499 return; | |
| 500 | |
| 501 // first fetch object to operate some cleanup functions on it | |
| 502 AXObject* obj = m_objects.get(axID); | |
| 503 if (!obj) | |
| 504 return; | |
| 505 | |
| 506 detachWrapper(obj); | |
| 507 obj->detach(); | |
| 508 removeAXID(obj); | |
| 509 | |
| 510 // finally remove the object | |
| 511 if (!m_objects.take(axID)) | |
| 512 return; | |
| 513 | |
| 514 ASSERT(m_objects.size() >= m_idsInUse.size()); | |
| 515 } | |
| 516 | |
| 517 void AXObjectCacheImpl::remove(RenderObject* renderer) | |
| 518 { | |
| 519 if (!renderer) | |
| 520 return; | |
| 521 | |
| 522 AXID axID = m_renderObjectMapping.get(renderer); | |
| 523 remove(axID); | |
| 524 m_renderObjectMapping.remove(renderer); | |
| 525 } | |
| 526 | |
| 527 void AXObjectCacheImpl::remove(Node* node) | |
| 528 { | |
| 529 if (!node) | |
| 530 return; | |
| 531 | |
| 532 removeNodeForUse(node); | |
| 533 | |
| 534 // This is all safe even if we didn't have a mapping. | |
| 535 AXID axID = m_nodeObjectMapping.get(node); | |
| 536 remove(axID); | |
| 537 m_nodeObjectMapping.remove(node); | |
| 538 | |
| 539 if (node->renderer()) { | |
| 540 remove(node->renderer()); | |
| 541 return; | |
| 542 } | |
| 543 } | |
| 544 | |
| 545 void AXObjectCacheImpl::remove(Widget* view) | |
| 546 { | |
| 547 if (!view) | |
| 548 return; | |
| 549 | |
| 550 AXID axID = m_widgetObjectMapping.get(view); | |
| 551 remove(axID); | |
| 552 m_widgetObjectMapping.remove(view); | |
| 553 } | |
| 554 | |
| 555 void AXObjectCacheImpl::remove(AbstractInlineTextBox* inlineTextBox) | |
| 556 { | |
| 557 if (!inlineTextBox) | |
| 558 return; | |
| 559 | |
| 560 AXID axID = m_inlineTextBoxObjectMapping.get(inlineTextBox); | |
| 561 remove(axID); | |
| 562 m_inlineTextBoxObjectMapping.remove(inlineTextBox); | |
| 563 } | |
| 564 | |
| 565 // FIXME: Oilpan: Use a weak hashmap for this instead. | |
| 566 void AXObjectCacheImpl::clearWeakMembers(Visitor* visitor) | |
| 567 { | |
| 568 Vector<Node*> deadNodes; | |
| 569 for (HashMap<Node*, AXID>::iterator it = m_nodeObjectMapping.begin(); it !=
m_nodeObjectMapping.end(); ++it) { | |
| 570 if (!visitor->isAlive(it->key)) | |
| 571 deadNodes.append(it->key); | |
| 572 } | |
| 573 for (unsigned i = 0; i < deadNodes.size(); ++i) | |
| 574 remove(deadNodes[i]); | |
| 575 | |
| 576 Vector<Widget*> deadWidgets; | |
| 577 for (HashMap<Widget*, AXID>::iterator it = m_widgetObjectMapping.begin(); | |
| 578 it != m_widgetObjectMapping.end(); ++it) { | |
| 579 if (!visitor->isAlive(it->key)) | |
| 580 deadWidgets.append(it->key); | |
| 581 } | |
| 582 for (unsigned i = 0; i < deadWidgets.size(); ++i) | |
| 583 remove(deadWidgets[i]); | |
| 584 } | |
| 585 | |
| 586 AXID AXObjectCacheImpl::platformGenerateAXID() const | |
| 587 { | |
| 588 static AXID lastUsedID = 0; | |
| 589 | |
| 590 // Generate a new ID. | |
| 591 AXID objID = lastUsedID; | |
| 592 do { | |
| 593 ++objID; | |
| 594 } while (!objID || HashTraits<AXID>::isDeletedValue(objID) || m_idsInUse.con
tains(objID)); | |
| 595 | |
| 596 lastUsedID = objID; | |
| 597 | |
| 598 return objID; | |
| 599 } | |
| 600 | |
| 601 AXID AXObjectCacheImpl::getAXID(AXObject* obj) | |
| 602 { | |
| 603 // check for already-assigned ID | |
| 604 AXID objID = obj->axObjectID(); | |
| 605 if (objID) { | |
| 606 ASSERT(m_idsInUse.contains(objID)); | |
| 607 return objID; | |
| 608 } | |
| 609 | |
| 610 objID = platformGenerateAXID(); | |
| 611 | |
| 612 m_idsInUse.add(objID); | |
| 613 obj->setAXObjectID(objID); | |
| 614 | |
| 615 return objID; | |
| 616 } | |
| 617 | |
| 618 void AXObjectCacheImpl::removeAXID(AXObject* object) | |
| 619 { | |
| 620 if (!object) | |
| 621 return; | |
| 622 | |
| 623 AXID objID = object->axObjectID(); | |
| 624 if (!objID) | |
| 625 return; | |
| 626 ASSERT(!HashTraits<AXID>::isDeletedValue(objID)); | |
| 627 ASSERT(m_idsInUse.contains(objID)); | |
| 628 object->setAXObjectID(0); | |
| 629 m_idsInUse.remove(objID); | |
| 630 } | |
| 631 | |
| 632 void AXObjectCacheImpl::selectionChanged(Node* node) | |
| 633 { | |
| 634 // Find the nearest ancestor that already has an accessibility object, since
we | |
| 635 // might be in the middle of a layout. | |
| 636 while (node) { | |
| 637 if (AXObject* obj = get(node)) { | |
| 638 obj->selectionChanged(); | |
| 639 return; | |
| 640 } | |
| 641 node = node->parentNode(); | |
| 642 } | |
| 643 } | |
| 644 | |
| 645 void AXObjectCacheImpl::textChanged(Node* node) | |
| 646 { | |
| 647 textChanged(getOrCreate(node)); | |
| 648 } | |
| 649 | |
| 650 void AXObjectCacheImpl::textChanged(RenderObject* renderer) | |
| 651 { | |
| 652 textChanged(getOrCreate(renderer)); | |
| 653 } | |
| 654 | |
| 655 void AXObjectCacheImpl::textChanged(AXObject* obj) | |
| 656 { | |
| 657 if (!obj) | |
| 658 return; | |
| 659 | |
| 660 bool parentAlreadyExists = obj->parentObjectIfExists(); | |
| 661 obj->textChanged(); | |
| 662 postNotification(obj, obj->document(), AXObjectCacheImpl::AXTextChanged, tru
e); | |
| 663 if (parentAlreadyExists) | |
| 664 obj->notifyIfIgnoredValueChanged(); | |
| 665 } | |
| 666 | |
| 667 void AXObjectCacheImpl::updateCacheAfterNodeIsAttached(Node* node) | |
| 668 { | |
| 669 // Calling get() will update the AX object if we had an AXNodeObject but now
we need | |
| 670 // an AXRenderObject, because it was reparented to a location outside of a c
anvas. | |
| 671 get(node); | |
| 672 } | |
| 673 | |
| 674 void AXObjectCacheImpl::childrenChanged(Node* node) | |
| 675 { | |
| 676 childrenChanged(get(node)); | |
| 677 } | |
| 678 | |
| 679 void AXObjectCacheImpl::childrenChanged(RenderObject* renderer) | |
| 680 { | |
| 681 childrenChanged(get(renderer)); | |
| 682 } | |
| 683 | |
| 684 void AXObjectCacheImpl::childrenChanged(Widget* widget) | |
| 685 { | |
| 686 childrenChanged(get(widget)); | |
| 687 } | |
| 688 | |
| 689 void AXObjectCacheImpl::childrenChanged(AXObject* obj) | |
| 690 { | |
| 691 if (!obj) | |
| 692 return; | |
| 693 | |
| 694 obj->childrenChanged(); | |
| 695 } | |
| 696 | |
| 697 void AXObjectCacheImpl::notificationPostTimerFired(Timer<AXObjectCacheImpl>*) | |
| 698 { | |
| 699 RefPtrWillBeRawPtr<Document> protectorForCacheOwner(m_document); | |
| 700 | |
| 701 m_notificationPostTimer.stop(); | |
| 702 | |
| 703 unsigned i = 0, count = m_notificationsToPost.size(); | |
| 704 for (i = 0; i < count; ++i) { | |
| 705 AXObject* obj = m_notificationsToPost[i].first.get(); | |
| 706 if (!obj->axObjectID()) | |
| 707 continue; | |
| 708 | |
| 709 if (!obj->axObjectCache()) | |
| 710 continue; | |
| 711 | |
| 712 #if ENABLE(ASSERT) | |
| 713 // Make sure none of the render views are in the process of being layed
out. | |
| 714 // Notifications should only be sent after the renderer has finished | |
| 715 if (obj->isAXRenderObject()) { | |
| 716 AXRenderObject* renderObj = toAXRenderObject(obj); | |
| 717 RenderObject* renderer = renderObj->renderer(); | |
| 718 if (renderer && renderer->view()) | |
| 719 ASSERT(!renderer->view()->layoutState()); | |
| 720 } | |
| 721 #endif | |
| 722 | |
| 723 AXNotification notification = m_notificationsToPost[i].second; | |
| 724 postPlatformNotification(obj, notification); | |
| 725 | |
| 726 if (notification == AXChildrenChanged && obj->parentObjectIfExists() &&
obj->lastKnownIsIgnoredValue() != obj->accessibilityIsIgnored()) | |
| 727 childrenChanged(obj->parentObject()); | |
| 728 } | |
| 729 | |
| 730 m_notificationsToPost.clear(); | |
| 731 } | |
| 732 | |
| 733 void AXObjectCacheImpl::postNotification(RenderObject* renderer, AXNotification
notification, bool postToElement) | |
| 734 { | |
| 735 if (!renderer) | |
| 736 return; | |
| 737 | |
| 738 m_modificationCount++; | |
| 739 | |
| 740 // Get an accessibility object that already exists. One should not be create
d here | |
| 741 // because a render update may be in progress and creating an AX object can
re-trigger a layout | |
| 742 RefPtr<AXObject> object = get(renderer); | |
| 743 while (!object && renderer) { | |
| 744 renderer = renderer->parent(); | |
| 745 object = get(renderer); | |
| 746 } | |
| 747 | |
| 748 if (!renderer) | |
| 749 return; | |
| 750 | |
| 751 postNotification(object.get(), &renderer->document(), notification, postToEl
ement); | |
| 752 } | |
| 753 | |
| 754 void AXObjectCacheImpl::postNotification(Node* node, AXNotification notification
, bool postToElement) | |
| 755 { | |
| 756 if (!node) | |
| 757 return; | |
| 758 | |
| 759 m_modificationCount++; | |
| 760 | |
| 761 // Get an accessibility object that already exists. One should not be create
d here | |
| 762 // because a render update may be in progress and creating an AX object can
re-trigger a layout | |
| 763 RefPtr<AXObject> object = get(node); | |
| 764 while (!object && node) { | |
| 765 node = node->parentNode(); | |
| 766 object = get(node); | |
| 767 } | |
| 768 | |
| 769 if (!node) | |
| 770 return; | |
| 771 | |
| 772 postNotification(object.get(), &node->document(), notification, postToElemen
t); | |
| 773 } | |
| 774 | |
| 775 void AXObjectCacheImpl::postNotification(AXObject* object, Document* document, A
XNotification notification, bool postToElement) | |
| 776 { | |
| 777 m_modificationCount++; | |
| 778 | |
| 779 if (object && !postToElement) | |
| 780 object = object->observableObject(); | |
| 781 | |
| 782 if (!object && document) | |
| 783 object = get(document->renderView()); | |
| 784 | |
| 785 if (!object) | |
| 786 return; | |
| 787 | |
| 788 m_notificationsToPost.append(std::make_pair(object, notification)); | |
| 789 if (!m_notificationPostTimer.isActive()) | |
| 790 m_notificationPostTimer.startOneShot(0, FROM_HERE); | |
| 791 } | |
| 792 | |
| 793 void AXObjectCacheImpl::checkedStateChanged(Node* node) | |
| 794 { | |
| 795 postNotification(node, AXObjectCacheImpl::AXCheckedStateChanged, true); | |
| 796 } | |
| 797 | |
| 798 void AXObjectCacheImpl::selectedChildrenChanged(Node* node) | |
| 799 { | |
| 800 // postToElement is false so that you can pass in any child of an element an
d it will go up the parent tree | |
| 801 // to find the container which should send out the notification. | |
| 802 postNotification(node, AXSelectedChildrenChanged, false); | |
| 803 } | |
| 804 | |
| 805 void AXObjectCacheImpl::selectedChildrenChanged(RenderObject* renderer) | |
| 806 { | |
| 807 // postToElement is false so that you can pass in any child of an element an
d it will go up the parent tree | |
| 808 // to find the container which should send out the notification. | |
| 809 postNotification(renderer, AXSelectedChildrenChanged, false); | |
| 810 } | |
| 811 | |
| 812 void AXObjectCacheImpl::handleScrollbarUpdate(FrameView* view) | |
| 813 { | |
| 814 if (!view) | |
| 815 return; | |
| 816 | |
| 817 // We don't want to create a scroll view from this method, only update an ex
isting one. | |
| 818 if (AXObject* scrollViewObject = get(view)) { | |
| 819 m_modificationCount++; | |
| 820 scrollViewObject->updateChildrenIfNecessary(); | |
| 821 } | |
| 822 } | |
| 823 | |
| 824 void AXObjectCacheImpl::handleLayoutComplete(RenderObject* renderer) | |
| 825 { | |
| 826 if (!renderer) | |
| 827 return; | |
| 828 | |
| 829 m_modificationCount++; | |
| 830 | |
| 831 // Create the AXObject if it didn't yet exist - that's always safe at the en
d of a layout, and it | |
| 832 // allows an AX notification to be sent when a page has its first layout, ra
ther than when the | |
| 833 // document first loads. | |
| 834 if (AXObject* obj = getOrCreate(renderer)) | |
| 835 postNotification(obj, obj->document(), AXLayoutComplete, true); | |
| 836 } | |
| 837 | |
| 838 void AXObjectCacheImpl::handleAriaExpandedChange(Node* node) | |
| 839 { | |
| 840 if (AXObject* obj = getOrCreate(node)) | |
| 841 obj->handleAriaExpandedChanged(); | |
| 842 } | |
| 843 | |
| 844 void AXObjectCacheImpl::handleActiveDescendantChanged(Node* node) | |
| 845 { | |
| 846 if (AXObject* obj = getOrCreate(node)) | |
| 847 obj->handleActiveDescendantChanged(); | |
| 848 } | |
| 849 | |
| 850 void AXObjectCacheImpl::handleAriaRoleChanged(Node* node) | |
| 851 { | |
| 852 if (AXObject* obj = getOrCreate(node)) { | |
| 853 obj->updateAccessibilityRole(); | |
| 854 m_modificationCount++; | |
| 855 obj->notifyIfIgnoredValueChanged(); | |
| 856 } | |
| 857 } | |
| 858 | |
| 859 void AXObjectCacheImpl::handleAttributeChanged(const QualifiedName& attrName, El
ement* element) | |
| 860 { | |
| 861 if (attrName == roleAttr) | |
| 862 handleAriaRoleChanged(element); | |
| 863 else if (attrName == altAttr || attrName == titleAttr) | |
| 864 textChanged(element); | |
| 865 else if (attrName == forAttr && isHTMLLabelElement(*element)) | |
| 866 labelChanged(element); | |
| 867 | |
| 868 if (!attrName.localName().startsWith("aria-")) | |
| 869 return; | |
| 870 | |
| 871 if (attrName == aria_activedescendantAttr) | |
| 872 handleActiveDescendantChanged(element); | |
| 873 else if (attrName == aria_valuenowAttr || attrName == aria_valuetextAttr) | |
| 874 postNotification(element, AXObjectCacheImpl::AXValueChanged, true); | |
| 875 else if (attrName == aria_labelAttr || attrName == aria_labeledbyAttr || att
rName == aria_labelledbyAttr) | |
| 876 textChanged(element); | |
| 877 else if (attrName == aria_checkedAttr) | |
| 878 checkedStateChanged(element); | |
| 879 else if (attrName == aria_selectedAttr) | |
| 880 selectedChildrenChanged(element); | |
| 881 else if (attrName == aria_expandedAttr) | |
| 882 handleAriaExpandedChange(element); | |
| 883 else if (attrName == aria_hiddenAttr) | |
| 884 childrenChanged(element->parentNode()); | |
| 885 else if (attrName == aria_invalidAttr) | |
| 886 postNotification(element, AXObjectCacheImpl::AXInvalidStatusChanged, tru
e); | |
| 887 else | |
| 888 postNotification(element, AXObjectCacheImpl::AXAriaAttributeChanged, tru
e); | |
| 889 } | |
| 890 | |
| 891 void AXObjectCacheImpl::labelChanged(Element* element) | |
| 892 { | |
| 893 textChanged(toHTMLLabelElement(element)->control()); | |
| 894 } | |
| 895 | |
| 896 void AXObjectCacheImpl::recomputeIsIgnored(RenderObject* renderer) | |
| 897 { | |
| 898 if (AXObject* obj = get(renderer)) | |
| 899 obj->notifyIfIgnoredValueChanged(); | |
| 900 } | |
| 901 | |
| 902 void AXObjectCacheImpl::inlineTextBoxesUpdated(RenderObject* renderer) | |
| 903 { | |
| 904 if (!inlineTextBoxAccessibilityEnabled()) | |
| 905 return; | |
| 906 | |
| 907 // Only update if the accessibility object already exists and it's | |
| 908 // not already marked as dirty. | |
| 909 if (AXObject* obj = get(renderer)) { | |
| 910 if (!obj->needsToUpdateChildren()) { | |
| 911 obj->setNeedsToUpdateChildren(); | |
| 912 postNotification(renderer, AXChildrenChanged, true); | |
| 913 } | |
| 914 } | |
| 915 } | |
| 916 | |
| 917 Settings* AXObjectCacheImpl::settings() | |
| 918 { | |
| 919 return m_document.settings(); | |
| 920 } | |
| 921 | |
| 922 bool AXObjectCacheImpl::accessibilityEnabled() | |
| 923 { | |
| 924 Settings* settings = this->settings(); | |
| 925 if (!settings) | |
| 926 return false; | |
| 927 return settings->accessibilityEnabled(); | |
| 928 } | |
| 929 | |
| 930 bool AXObjectCacheImpl::inlineTextBoxAccessibilityEnabled() | |
| 931 { | |
| 932 Settings* settings = this->settings(); | |
| 933 if (!settings) | |
| 934 return false; | |
| 935 return settings->inlineTextBoxAccessibilityEnabled(); | |
| 936 } | |
| 937 | |
| 938 const Element* AXObjectCacheImpl::rootAXEditableElement(const Node* node) | |
| 939 { | |
| 940 const Element* result = node->rootEditableElement(); | |
| 941 const Element* element = node->isElementNode() ? toElement(node) : node->par
entElement(); | |
| 942 | |
| 943 for (; element; element = element->parentElement()) { | |
| 944 if (nodeIsTextControl(element)) | |
| 945 result = element; | |
| 946 } | |
| 947 | |
| 948 return result; | |
| 949 } | |
| 950 | |
| 951 bool AXObjectCacheImpl::nodeIsTextControl(const Node* node) | |
| 952 { | |
| 953 if (!node) | |
| 954 return false; | |
| 955 | |
| 956 const AXObject* axObject = getOrCreate(const_cast<Node*>(node)); | |
| 957 return axObject && axObject->isTextControl(); | |
| 958 } | |
| 959 | |
| 960 bool isNodeAriaVisible(Node* node) | |
| 961 { | |
| 962 if (!node) | |
| 963 return false; | |
| 964 | |
| 965 if (!node->isElementNode()) | |
| 966 return false; | |
| 967 | |
| 968 return equalIgnoringCase(toElement(node)->getAttribute(aria_hiddenAttr), "fa
lse"); | |
| 969 } | |
| 970 | |
| 971 void AXObjectCacheImpl::detachWrapper(AXObject* obj) | |
| 972 { | |
| 973 // In Chromium, AXObjects are not wrapped. | |
| 974 } | |
| 975 | |
| 976 void AXObjectCacheImpl::attachWrapper(AXObject*) | |
| 977 { | |
| 978 // In Chromium, AXObjects are not wrapped. | |
| 979 } | |
| 980 | |
| 981 void AXObjectCacheImpl::postPlatformNotification(AXObject* obj, AXNotification n
otification) | |
| 982 { | |
| 983 if (obj && obj->isAXScrollbar() && notification == AXValueChanged) { | |
| 984 // Send document value changed on scrollbar value changed notification. | |
| 985 Scrollbar* scrollBar = toAXScrollbar(obj)->scrollbar(); | |
| 986 if (!scrollBar || !scrollBar->parent() || !scrollBar->parent()->isFrameV
iew()) | |
| 987 return; | |
| 988 Document* document = toFrameView(scrollBar->parent())->frame().document(
); | |
| 989 if (document != document->topDocument()) | |
| 990 return; | |
| 991 obj = get(document->renderView()); | |
| 992 } | |
| 993 | |
| 994 if (!obj || !obj->document() || !obj->documentFrameView() || !obj->documentF
rameView()->frame().page()) | |
| 995 return; | |
| 996 | |
| 997 ChromeClient& client = obj->document()->axObjectCacheOwner().page()->chrome(
).client(); | |
| 998 | |
| 999 if (notification == AXActiveDescendantChanged | |
| 1000 && obj->document()->focusedElement() | |
| 1001 && obj->node() == obj->document()->focusedElement()) { | |
| 1002 // Calling handleFocusedUIElementChanged will focus the new active | |
| 1003 // descendant and send the AXFocusedUIElementChanged notification. | |
| 1004 handleFocusedUIElementChanged(0, obj->document()->focusedElement()); | |
| 1005 } | |
| 1006 | |
| 1007 client.postAccessibilityNotification(obj, notification); | |
| 1008 } | |
| 1009 | |
| 1010 void AXObjectCacheImpl::handleFocusedUIElementChanged(Node*, Node* newFocusedNod
e) | |
| 1011 { | |
| 1012 if (!newFocusedNode) | |
| 1013 return; | |
| 1014 | |
| 1015 Page* page = newFocusedNode->document().page(); | |
| 1016 if (!page) | |
| 1017 return; | |
| 1018 | |
| 1019 AXObject* focusedObject = focusedUIElementForPage(page); | |
| 1020 if (!focusedObject) | |
| 1021 return; | |
| 1022 | |
| 1023 postPlatformNotification(focusedObject, AXFocusedUIElementChanged); | |
| 1024 } | |
| 1025 | |
| 1026 void AXObjectCacheImpl::handleInitialFocus() | |
| 1027 { | |
| 1028 postNotification(&m_document, AXObjectCache::AXFocusedUIElementChanged, true
); | |
| 1029 } | |
| 1030 | |
| 1031 void AXObjectCacheImpl::handleEditableTextContentChanged(Node* node) | |
| 1032 { | |
| 1033 postNotification(node, AXObjectCache::AXValueChanged, false); | |
| 1034 } | |
| 1035 | |
| 1036 void AXObjectCacheImpl::handleTextFormControlChanged(Node* node) | |
| 1037 { | |
| 1038 postNotification(node, AXObjectCache::AXValueChanged, false); | |
| 1039 } | |
| 1040 | |
| 1041 void AXObjectCacheImpl::handleValueChanged(Node* node) | |
| 1042 { | |
| 1043 postNotification(node, AXObjectCache::AXValueChanged, true); | |
| 1044 } | |
| 1045 | |
| 1046 void AXObjectCacheImpl::handleUpdateActiveMenuOption(RenderMenuList* menuList, i
nt optionIndex) | |
| 1047 { | |
| 1048 if (AXMenuList* axMenuList = static_cast<AXMenuList*>(get(menuList))) | |
| 1049 axMenuList->didUpdateActiveOption(optionIndex); | |
| 1050 } | |
| 1051 | |
| 1052 void AXObjectCacheImpl::handleLoadComplete(Document* document) | |
| 1053 { | |
| 1054 postNotification(getOrCreate(document), document, AXObjectCache::AXLoadCompl
ete, true); | |
| 1055 } | |
| 1056 | |
| 1057 void AXObjectCacheImpl::handleLayoutComplete(Document* document) | |
| 1058 { | |
| 1059 postNotification(getOrCreate(document), document, AXObjectCache::AXLayoutCom
plete, true); | |
| 1060 } | |
| 1061 | |
| 1062 void AXObjectCacheImpl::handleScrolledToAnchor(const Node* anchorNode) | |
| 1063 { | |
| 1064 // The anchor node may not be accessible. Post the notification for the | |
| 1065 // first accessible object. | |
| 1066 postPlatformNotification(AXObject::firstAccessibleObjectFromNode(anchorNode)
, AXScrolledToAnchor); | |
| 1067 } | |
| 1068 | |
| 1069 void AXObjectCacheImpl::handleScrollPositionChanged(FrameView* frameView) | |
| 1070 { | |
| 1071 // Prefer to fire the scroll position changed event on the frame view's chil
d web area, if possible. | |
| 1072 AXObject* targetAXObject = getOrCreate(frameView); | |
| 1073 if (targetAXObject && !targetAXObject->children().isEmpty()) | |
| 1074 targetAXObject = targetAXObject->children()[0].get(); | |
| 1075 postPlatformNotification(targetAXObject, AXScrollPositionChanged); | |
| 1076 } | |
| 1077 | |
| 1078 void AXObjectCacheImpl::handleScrollPositionChanged(RenderObject* renderObject) | |
| 1079 { | |
| 1080 postPlatformNotification(getOrCreate(renderObject), AXScrollPositionChanged)
; | |
| 1081 } | |
| 1082 | |
| 1083 void AXObjectCacheImpl::setCanvasObjectBounds(Element* element, const LayoutRect
& rect) | |
| 1084 { | |
| 1085 AXObject* obj = getOrCreate(element); | |
| 1086 if (!obj) | |
| 1087 return; | |
| 1088 | |
| 1089 obj->setElementRect(rect); | |
| 1090 } | |
| 1091 | |
| 1092 } // namespace blink | |
| OLD | NEW |