OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2008 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 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 #include "core/accessibility/AXRenderObject.h" | |
31 | |
32 #include "bindings/core/v8/ExceptionStatePlaceholder.h" | |
33 #include "core/InputTypeNames.h" | |
34 #include "core/accessibility/AXImageMapLink.h" | |
35 #include "core/accessibility/AXInlineTextBox.h" | |
36 #include "core/accessibility/AXObjectCacheImpl.h" | |
37 #include "core/accessibility/AXSVGRoot.h" | |
38 #include "core/accessibility/AXSpinButton.h" | |
39 #include "core/accessibility/AXTable.h" | |
40 #include "core/dom/ElementTraversal.h" | |
41 #include "core/dom/shadow/ShadowRoot.h" | |
42 #include "core/editing/FrameSelection.h" | |
43 #include "core/editing/RenderedPosition.h" | |
44 #include "core/editing/TextIterator.h" | |
45 #include "core/editing/VisibleUnits.h" | |
46 #include "core/editing/htmlediting.h" | |
47 #include "core/frame/LocalFrame.h" | |
48 #include "core/frame/Settings.h" | |
49 #include "core/html/HTMLImageElement.h" | |
50 #include "core/html/HTMLLabelElement.h" | |
51 #include "core/html/HTMLOptionElement.h" | |
52 #include "core/html/HTMLSelectElement.h" | |
53 #include "core/html/HTMLTextAreaElement.h" | |
54 #include "core/html/shadow/ShadowElementNames.h" | |
55 #include "core/loader/ProgressTracker.h" | |
56 #include "core/page/Page.h" | |
57 #include "core/rendering/HitTestResult.h" | |
58 #include "core/rendering/RenderFieldset.h" | |
59 #include "core/rendering/RenderFileUploadControl.h" | |
60 #include "core/rendering/RenderHTMLCanvas.h" | |
61 #include "core/rendering/RenderImage.h" | |
62 #include "core/rendering/RenderInline.h" | |
63 #include "core/rendering/RenderLayer.h" | |
64 #include "core/rendering/RenderListMarker.h" | |
65 #include "core/rendering/RenderMenuList.h" | |
66 #include "core/rendering/RenderPart.h" | |
67 #include "core/rendering/RenderTextControlSingleLine.h" | |
68 #include "core/rendering/RenderTextFragment.h" | |
69 #include "core/rendering/RenderView.h" | |
70 #include "core/svg/SVGDocumentExtensions.h" | |
71 #include "core/svg/SVGSVGElement.h" | |
72 #include "core/svg/graphics/SVGImage.h" | |
73 #include "platform/text/PlatformLocale.h" | |
74 #include "wtf/StdLibExtras.h" | |
75 | |
76 using blink::WebLocalizedString; | |
77 | |
78 namespace blink { | |
79 | |
80 using namespace HTMLNames; | |
81 | |
82 static inline RenderObject* firstChildInContinuation(const RenderInline& rendere
r) | |
83 { | |
84 RenderBoxModelObject* r = renderer.continuation(); | |
85 | |
86 while (r) { | |
87 if (r->isRenderBlock()) | |
88 return r; | |
89 if (RenderObject* child = r->slowFirstChild()) | |
90 return child; | |
91 r = toRenderInline(r)->continuation(); | |
92 } | |
93 | |
94 return 0; | |
95 } | |
96 | |
97 static inline bool isInlineWithContinuation(RenderObject* object) | |
98 { | |
99 if (!object->isBoxModelObject()) | |
100 return false; | |
101 | |
102 RenderBoxModelObject* renderer = toRenderBoxModelObject(object); | |
103 if (!renderer->isRenderInline()) | |
104 return false; | |
105 | |
106 return toRenderInline(renderer)->continuation(); | |
107 } | |
108 | |
109 static inline RenderObject* firstChildConsideringContinuation(RenderObject* rend
erer) | |
110 { | |
111 RenderObject* firstChild = renderer->slowFirstChild(); | |
112 | |
113 if (!firstChild && isInlineWithContinuation(renderer)) | |
114 firstChild = firstChildInContinuation(toRenderInline(*renderer)); | |
115 | |
116 return firstChild; | |
117 } | |
118 | |
119 static inline RenderInline* startOfContinuations(RenderObject* r) | |
120 { | |
121 if (r->isInlineElementContinuation()) { | |
122 return toRenderInline(r->node()->renderer()); | |
123 } | |
124 | |
125 // Blocks with a previous continuation always have a next continuation | |
126 if (r->isRenderBlock() && toRenderBlock(r)->inlineElementContinuation()) | |
127 return toRenderInline(toRenderBlock(r)->inlineElementContinuation()->nod
e()->renderer()); | |
128 | |
129 return 0; | |
130 } | |
131 | |
132 static inline RenderObject* endOfContinuations(RenderObject* renderer) | |
133 { | |
134 RenderObject* prev = renderer; | |
135 RenderObject* cur = renderer; | |
136 | |
137 if (!cur->isRenderInline() && !cur->isRenderBlock()) | |
138 return renderer; | |
139 | |
140 while (cur) { | |
141 prev = cur; | |
142 if (cur->isRenderInline()) { | |
143 cur = toRenderInline(cur)->inlineElementContinuation(); | |
144 ASSERT(cur || !toRenderInline(prev)->continuation()); | |
145 } else { | |
146 cur = toRenderBlock(cur)->inlineElementContinuation(); | |
147 } | |
148 } | |
149 | |
150 return prev; | |
151 } | |
152 | |
153 static inline bool lastChildHasContinuation(RenderObject* renderer) | |
154 { | |
155 RenderObject* lastChild = renderer->slowLastChild(); | |
156 return lastChild && isInlineWithContinuation(lastChild); | |
157 } | |
158 | |
159 static RenderBoxModelObject* nextContinuation(RenderObject* renderer) | |
160 { | |
161 ASSERT(renderer); | |
162 if (renderer->isRenderInline() && !renderer->isReplaced()) | |
163 return toRenderInline(renderer)->continuation(); | |
164 if (renderer->isRenderBlock()) | |
165 return toRenderBlock(renderer)->inlineElementContinuation(); | |
166 return 0; | |
167 } | |
168 | |
169 AXRenderObject::AXRenderObject(RenderObject* renderer) | |
170 : AXNodeObject(renderer->node()) | |
171 , m_renderer(renderer) | |
172 , m_cachedElementRectDirty(true) | |
173 { | |
174 #if ENABLE(ASSERT) | |
175 m_renderer->setHasAXObject(true); | |
176 #endif | |
177 } | |
178 | |
179 PassRefPtr<AXRenderObject> AXRenderObject::create(RenderObject* renderer) | |
180 { | |
181 return adoptRef(new AXRenderObject(renderer)); | |
182 } | |
183 | |
184 AXRenderObject::~AXRenderObject() | |
185 { | |
186 ASSERT(isDetached()); | |
187 } | |
188 | |
189 LayoutRect AXRenderObject::elementRect() const | |
190 { | |
191 if (!m_explicitElementRect.isEmpty()) | |
192 return m_explicitElementRect; | |
193 if (!m_renderer) | |
194 return LayoutRect(); | |
195 if (!m_renderer->isBox()) | |
196 return computeElementRect(); | |
197 | |
198 for (const AXObject* obj = this; obj; obj = obj->parentObject()) { | |
199 if (obj->isAXRenderObject()) | |
200 toAXRenderObject(obj)->checkCachedElementRect(); | |
201 } | |
202 for (const AXObject* obj = this; obj; obj = obj->parentObject()) { | |
203 if (obj->isAXRenderObject()) | |
204 toAXRenderObject(obj)->updateCachedElementRect(); | |
205 } | |
206 | |
207 return m_cachedElementRect; | |
208 } | |
209 | |
210 void AXRenderObject::setRenderer(RenderObject* renderer) | |
211 { | |
212 m_renderer = renderer; | |
213 setNode(renderer->node()); | |
214 } | |
215 | |
216 RenderBoxModelObject* AXRenderObject::renderBoxModelObject() const | |
217 { | |
218 if (!m_renderer || !m_renderer->isBoxModelObject()) | |
219 return 0; | |
220 return toRenderBoxModelObject(m_renderer); | |
221 } | |
222 | |
223 Document* AXRenderObject::topDocument() const | |
224 { | |
225 if (!document()) | |
226 return 0; | |
227 return &document()->topDocument(); | |
228 } | |
229 | |
230 bool AXRenderObject::shouldNotifyActiveDescendant() const | |
231 { | |
232 // We want to notify that the combo box has changed its active descendant, | |
233 // but we do not want to change the focus, because focus should remain with
the combo box. | |
234 if (isComboBox()) | |
235 return true; | |
236 | |
237 return shouldFocusActiveDescendant(); | |
238 } | |
239 | |
240 ScrollableArea* AXRenderObject::getScrollableAreaIfScrollable() const | |
241 { | |
242 // If the parent is a FrameView, then this object isn't really scrollable; t
he parent should handle the scrolling. | |
243 if (parentObject() && parentObject()->isAXScrollView()) | |
244 return 0; | |
245 | |
246 if (!m_renderer || !m_renderer->isBox()) | |
247 return 0; | |
248 | |
249 RenderBox* box = toRenderBox(m_renderer); | |
250 if (!box->canBeScrolledAndHasScrollableArea()) | |
251 return 0; | |
252 | |
253 return box->scrollableArea(); | |
254 } | |
255 | |
256 AccessibilityRole AXRenderObject::determineAccessibilityRole() | |
257 { | |
258 if (!m_renderer) | |
259 return UnknownRole; | |
260 | |
261 m_ariaRole = determineAriaRoleAttribute(); | |
262 | |
263 Node* node = m_renderer->node(); | |
264 AccessibilityRole ariaRole = ariaRoleAttribute(); | |
265 if (ariaRole != UnknownRole) | |
266 return ariaRole; | |
267 | |
268 RenderBoxModelObject* cssBox = renderBoxModelObject(); | |
269 | |
270 if (node && node->isLink()) { | |
271 if (cssBox && cssBox->isImage()) | |
272 return ImageMapRole; | |
273 return LinkRole; | |
274 } | |
275 if ((cssBox && cssBox->isListItem()) || isHTMLLIElement(node)) | |
276 return ListItemRole; | |
277 if (m_renderer->isListMarker()) | |
278 return ListMarkerRole; | |
279 if (isHTMLButtonElement(node)) | |
280 return buttonRoleType(); | |
281 if (isHTMLDetailsElement(node)) | |
282 return DetailsRole; | |
283 if (isHTMLSummaryElement(node)) { | |
284 if (node->parentElement() && isHTMLDetailsElement(node->parentElement())
) | |
285 return DisclosureTriangleRole; | |
286 return UnknownRole; | |
287 } | |
288 if (isHTMLLegendElement(node)) | |
289 return LegendRole; | |
290 if (m_renderer->isText()) | |
291 return StaticTextRole; | |
292 if (cssBox && cssBox->isImage()) { | |
293 if (isHTMLInputElement(node)) | |
294 return ariaHasPopup() ? PopUpButtonRole : ButtonRole; | |
295 if (isSVGImage()) | |
296 return SVGRootRole; | |
297 return ImageRole; | |
298 } | |
299 | |
300 // Note: if JavaScript is disabled, the renderer won't be a RenderHTMLCanvas
. | |
301 if (isHTMLCanvasElement(node) && m_renderer->isCanvas()) | |
302 return CanvasRole; | |
303 | |
304 if (cssBox && cssBox->isRenderView()) | |
305 return WebAreaRole; | |
306 | |
307 if (cssBox && cssBox->isTextField()) | |
308 return TextFieldRole; | |
309 | |
310 if (cssBox && cssBox->isTextArea()) | |
311 return TextAreaRole; | |
312 | |
313 if (isHTMLInputElement(node)) { | |
314 HTMLInputElement& input = toHTMLInputElement(*node); | |
315 const AtomicString& type = input.type(); | |
316 if (type == InputTypeNames::button) { | |
317 if ((node->parentNode() && isHTMLMenuElement(node->parentNode())) ||
(parentObject() && parentObject()->roleValue() == MenuRole)) | |
318 return MenuItemRole; | |
319 return buttonRoleType(); | |
320 } | |
321 if (type == InputTypeNames::checkbox) { | |
322 if ((node->parentNode() && isHTMLMenuElement(node->parentNode())) ||
(parentObject() && parentObject()->roleValue() == MenuRole)) | |
323 return MenuItemCheckBoxRole; | |
324 return CheckBoxRole; | |
325 } | |
326 if (type == InputTypeNames::date) | |
327 return DateRole; | |
328 if (type == InputTypeNames::datetime | |
329 || type == InputTypeNames::datetime_local | |
330 || type == InputTypeNames::month | |
331 || type == InputTypeNames::week) | |
332 return DateTimeRole; | |
333 if (type == InputTypeNames::radio) { | |
334 if ((node->parentNode() && isHTMLMenuElement(node->parentNode())) ||
(parentObject() && parentObject()->roleValue() == MenuRole)) | |
335 return MenuItemRadioRole; | |
336 return RadioButtonRole; | |
337 } | |
338 if (input.isTextButton()) | |
339 return buttonRoleType(); | |
340 if (type == InputTypeNames::color) | |
341 return ColorWellRole; | |
342 if (type == InputTypeNames::time) | |
343 return TimeRole; | |
344 } | |
345 | |
346 if (isFileUploadButton()) | |
347 return ButtonRole; | |
348 | |
349 if (cssBox && cssBox->isMenuList()) | |
350 return PopUpButtonRole; | |
351 | |
352 if (headingLevel()) | |
353 return HeadingRole; | |
354 | |
355 if (m_renderer->isSVGImage()) | |
356 return ImageRole; | |
357 if (m_renderer->isSVGRoot()) | |
358 return SVGRootRole; | |
359 | |
360 if (node && node->hasTagName(ddTag)) | |
361 return DescriptionListDetailRole; | |
362 | |
363 if (node && node->hasTagName(dlTag)) | |
364 return DescriptionListRole; | |
365 | |
366 if (node && node->hasTagName(dtTag)) | |
367 return DescriptionListTermRole; | |
368 | |
369 if (node && (node->hasTagName(rpTag) || node->hasTagName(rtTag))) | |
370 return AnnotationRole; | |
371 | |
372 // Table sections should be ignored. | |
373 if (m_renderer->isTableSection()) | |
374 return IgnoredRole; | |
375 | |
376 if (m_renderer->isHR()) | |
377 return HorizontalRuleRole; | |
378 | |
379 if (isHTMLOutputElement(node)) | |
380 return StatusRole; | |
381 | |
382 if (isHTMLParagraphElement(node)) | |
383 return ParagraphRole; | |
384 | |
385 if (isHTMLLabelElement(node)) | |
386 return LabelRole; | |
387 | |
388 if (isHTMLRubyElement(node)) | |
389 return RubyRole; | |
390 | |
391 if (isHTMLDivElement(node)) | |
392 return DivRole; | |
393 | |
394 if (isHTMLMeterElement(node)) | |
395 return MeterRole; | |
396 | |
397 if (isHTMLFormElement(node)) | |
398 return FormRole; | |
399 | |
400 if (node && node->hasTagName(articleTag)) | |
401 return ArticleRole; | |
402 | |
403 if (node && node->hasTagName(blockquoteTag)) | |
404 return BlockquoteRole; | |
405 | |
406 if (node && node->hasTagName(mainTag)) | |
407 return MainRole; | |
408 | |
409 if (node && node->hasTagName(navTag)) | |
410 return NavigationRole; | |
411 | |
412 if (node && node->hasTagName(asideTag)) | |
413 return ComplementaryRole; | |
414 | |
415 if (node && node->hasTagName(preTag)) | |
416 return PreRole; | |
417 | |
418 if (node && node->hasTagName(sectionTag)) | |
419 return RegionRole; | |
420 | |
421 if (node && node->hasTagName(addressTag)) | |
422 return ContentInfoRole; | |
423 | |
424 if (node && node->hasTagName(dialogTag)) | |
425 return DialogRole; | |
426 | |
427 // The HTML element should not be exposed as an element. That's what the Ren
derView element does. | |
428 if (isHTMLHtmlElement(node)) | |
429 return IgnoredRole; | |
430 | |
431 if (node && node->hasTagName(iframeTag)) | |
432 return IframeRole; | |
433 | |
434 if (isEmbeddedObject()) | |
435 return EmbeddedObjectRole; | |
436 | |
437 if (node && node->hasTagName(figcaptionTag)) | |
438 return FigcaptionRole; | |
439 | |
440 if (node && node->hasTagName(figureTag)) | |
441 return FigureRole; | |
442 | |
443 // There should only be one banner/contentInfo per page. If header/footer ar
e being used within an article or section | |
444 // then it should not be exposed as whole page's banner/contentInfo | |
445 if (node && node->hasTagName(headerTag) && !isDescendantOfElementType(articl
eTag) && !isDescendantOfElementType(sectionTag)) | |
446 return BannerRole; | |
447 if (node && node->hasTagName(footerTag) && !isDescendantOfElementType(articl
eTag) && !isDescendantOfElementType(sectionTag)) | |
448 return FooterRole; | |
449 | |
450 if (isHTMLAnchorElement(node) && isClickable()) | |
451 return LinkRole; | |
452 | |
453 if (m_renderer->isRenderBlockFlow()) | |
454 return GroupRole; | |
455 | |
456 // If the element does not have role, but it has ARIA attributes, accessibil
ity should fallback to exposing it as a group. | |
457 if (supportsARIAAttributes()) | |
458 return GroupRole; | |
459 | |
460 return UnknownRole; | |
461 } | |
462 | |
463 void AXRenderObject::init() | |
464 { | |
465 AXNodeObject::init(); | |
466 } | |
467 | |
468 void AXRenderObject::detach() | |
469 { | |
470 AXNodeObject::detach(); | |
471 | |
472 detachRemoteSVGRoot(); | |
473 | |
474 #if ENABLE(ASSERT) | |
475 if (m_renderer) | |
476 m_renderer->setHasAXObject(false); | |
477 #endif | |
478 m_renderer = 0; | |
479 } | |
480 | |
481 // | |
482 // Check object role or purpose. | |
483 // | |
484 | |
485 bool AXRenderObject::isAttachment() const | |
486 { | |
487 RenderBoxModelObject* renderer = renderBoxModelObject(); | |
488 if (!renderer) | |
489 return false; | |
490 // Widgets are the replaced elements that we represent to AX as attachments | |
491 bool isRenderPart = renderer->isRenderPart(); | |
492 ASSERT(!isRenderPart || (renderer->isReplaced() && !isImage())); | |
493 return isRenderPart; | |
494 } | |
495 | |
496 bool AXRenderObject::isFileUploadButton() const | |
497 { | |
498 return m_renderer && isHTMLInputElement(m_renderer->node()) && toHTMLInputEl
ement(*m_renderer->node()).type() == InputTypeNames::file; | |
499 } | |
500 | |
501 static bool isLinkable(const AXObject& object) | |
502 { | |
503 if (!object.renderer()) | |
504 return false; | |
505 | |
506 // See https://wiki.mozilla.org/Accessibility/AT-Windows-API for the element
s | |
507 // Mozilla considers linkable. | |
508 return object.isLink() || object.isImage() || object.renderer()->isText(); | |
509 } | |
510 | |
511 bool AXRenderObject::isLinked() const | |
512 { | |
513 if (!isLinkable(*this)) | |
514 return false; | |
515 | |
516 Element* anchor = anchorElement(); | |
517 if (!isHTMLAnchorElement(anchor)) | |
518 return false; | |
519 | |
520 return !toHTMLAnchorElement(*anchor).href().isEmpty(); | |
521 } | |
522 | |
523 bool AXRenderObject::isLoaded() const | |
524 { | |
525 return !m_renderer->document().parser(); | |
526 } | |
527 | |
528 bool AXRenderObject::isOffScreen() const | |
529 { | |
530 ASSERT(m_renderer); | |
531 IntRect contentRect = pixelSnappedIntRect(m_renderer->absoluteClippedOverflo
wRect()); | |
532 FrameView* view = m_renderer->frame()->view(); | |
533 IntRect viewRect = view->visibleContentRect(); | |
534 viewRect.intersect(contentRect); | |
535 return viewRect.isEmpty(); | |
536 } | |
537 | |
538 bool AXRenderObject::isReadOnly() const | |
539 { | |
540 ASSERT(m_renderer); | |
541 | |
542 if (isWebArea()) { | |
543 Document& document = m_renderer->document(); | |
544 HTMLElement* body = document.body(); | |
545 if (body && body->hasEditableStyle()) | |
546 return false; | |
547 | |
548 return !document.hasEditableStyle(); | |
549 } | |
550 | |
551 return AXNodeObject::isReadOnly(); | |
552 } | |
553 | |
554 bool AXRenderObject::isVisited() const | |
555 { | |
556 // FIXME: Is it a privacy violation to expose visited information to accessi
bility APIs? | |
557 return m_renderer->style()->isLink() && m_renderer->style()->insideLink() ==
InsideVisitedLink; | |
558 } | |
559 | |
560 // | |
561 // Check object state. | |
562 // | |
563 | |
564 bool AXRenderObject::isFocused() const | |
565 { | |
566 if (!m_renderer) | |
567 return false; | |
568 | |
569 Document& document = m_renderer->document(); | |
570 Element* focusedElement = document.focusedElement(); | |
571 if (!focusedElement) | |
572 return false; | |
573 | |
574 // A web area is represented by the Document node in the DOM tree, which isn
't focusable. | |
575 // Check instead if the frame's selection controller is focused | |
576 if (focusedElement == m_renderer->node() | |
577 || (roleValue() == WebAreaRole && document.frame()->selection().isFocuse
dAndActive())) | |
578 return true; | |
579 | |
580 return false; | |
581 } | |
582 | |
583 bool AXRenderObject::isSelected() const | |
584 { | |
585 if (!m_renderer) | |
586 return false; | |
587 | |
588 Node* node = m_renderer->node(); | |
589 if (!node) | |
590 return false; | |
591 | |
592 const AtomicString& ariaSelected = getAttribute(aria_selectedAttr); | |
593 if (equalIgnoringCase(ariaSelected, "true")) | |
594 return true; | |
595 | |
596 if (isTabItem() && isTabItemSelected()) | |
597 return true; | |
598 | |
599 return false; | |
600 } | |
601 | |
602 // | |
603 // Whether objects are ignored, i.e. not included in the tree. | |
604 // | |
605 | |
606 AXObjectInclusion AXRenderObject::defaultObjectInclusion() const | |
607 { | |
608 // The following cases can apply to any element that's a subclass of AXRende
rObject. | |
609 | |
610 if (!m_renderer) | |
611 return IgnoreObject; | |
612 | |
613 if (m_renderer->style()->visibility() != VISIBLE) { | |
614 // aria-hidden is meant to override visibility as the determinant in AX
hierarchy inclusion. | |
615 if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "false")) | |
616 return DefaultBehavior; | |
617 | |
618 return IgnoreObject; | |
619 } | |
620 | |
621 return AXObject::defaultObjectInclusion(); | |
622 } | |
623 | |
624 bool AXRenderObject::computeAccessibilityIsIgnored() const | |
625 { | |
626 #if ENABLE(ASSERT) | |
627 ASSERT(m_initialized); | |
628 #endif | |
629 | |
630 // Check first if any of the common reasons cause this element to be ignored
. | |
631 // Then process other use cases that need to be applied to all the various r
oles | |
632 // that AXRenderObjects take on. | |
633 AXObjectInclusion decision = defaultObjectInclusion(); | |
634 if (decision == IncludeObject) | |
635 return false; | |
636 if (decision == IgnoreObject) | |
637 return true; | |
638 | |
639 // If this element is within a parent that cannot have children, it should n
ot be exposed. | |
640 if (isDescendantOfBarrenParent()) | |
641 return true; | |
642 | |
643 if (roleValue() == IgnoredRole) | |
644 return true; | |
645 | |
646 if ((roleValue() == NoneRole || roleValue() == PresentationalRole) || inheri
tsPresentationalRole()) | |
647 return true; | |
648 | |
649 // An ARIA tree can only have tree items and static text as children. | |
650 if (!isAllowedChildOfTree()) | |
651 return true; | |
652 | |
653 // TODO: we should refactor this - but right now this is necessary to make | |
654 // sure scroll areas stay in the tree. | |
655 if (isAttachment()) | |
656 return false; | |
657 | |
658 // ignore popup menu items because AppKit does | |
659 for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->p
arent()) { | |
660 if (parent->isBoxModelObject() && toRenderBoxModelObject(parent)->isMenu
List()) | |
661 return true; | |
662 } | |
663 | |
664 // find out if this element is inside of a label element. | |
665 // if so, it may be ignored because it's the label for a checkbox or radio b
utton | |
666 AXObject* controlObject = correspondingControlForLabelElement(); | |
667 if (controlObject && !controlObject->exposesTitleUIElement() && controlObjec
t->isCheckboxOrRadio()) | |
668 return true; | |
669 | |
670 // NOTE: BRs always have text boxes now, so the text box check here can be r
emoved | |
671 if (m_renderer->isText()) { | |
672 // static text beneath MenuItems and MenuButtons are just reported along
with the menu item, so it's ignored on an individual level | |
673 AXObject* parent = parentObjectUnignored(); | |
674 if (parent && (parent->ariaRoleAttribute() == MenuItemRole || parent->ar
iaRoleAttribute() == MenuButtonRole)) | |
675 return true; | |
676 RenderText* renderText = toRenderText(m_renderer); | |
677 if (m_renderer->isBR() || !renderText->firstTextBox()) | |
678 return true; | |
679 | |
680 // Don't ignore static text in editable text controls. | |
681 for (AXObject* parent = parentObject(); parent; parent = parent->parentO
bject()) { | |
682 if (parent->roleValue() == TextFieldRole || parent->roleValue() == T
extAreaRole) | |
683 return false; | |
684 } | |
685 | |
686 // text elements that are just empty whitespace should not be returned | |
687 // FIXME(dmazzoni): we probably shouldn't ignore this if the style is 'p
re', or similar... | |
688 return renderText->text().impl()->containsOnlyWhitespace(); | |
689 } | |
690 | |
691 if (isHeading()) | |
692 return false; | |
693 | |
694 if (isLandmarkRelated()) | |
695 return false; | |
696 | |
697 if (isLink()) | |
698 return false; | |
699 | |
700 // all controls are accessible | |
701 if (isControl()) | |
702 return false; | |
703 | |
704 if (ariaRoleAttribute() != UnknownRole) | |
705 return false; | |
706 | |
707 // don't ignore labels, because they serve as TitleUIElements | |
708 Node* node = m_renderer->node(); | |
709 if (isHTMLLabelElement(node)) | |
710 return false; | |
711 | |
712 // Anything that is content editable should not be ignored. | |
713 // However, one cannot just call node->hasEditableStyle() since that will as
k if its parents | |
714 // are also editable. Only the top level content editable region should be e
xposed. | |
715 if (hasContentEditableAttributeSet()) | |
716 return false; | |
717 | |
718 // List items play an important role in defining the structure of lists. The
y should not be ignored. | |
719 if (roleValue() == ListItemRole) | |
720 return false; | |
721 | |
722 if (roleValue() == BlockquoteRole) | |
723 return false; | |
724 | |
725 if (roleValue() == DialogRole) | |
726 return false; | |
727 | |
728 if (roleValue() == FigcaptionRole) | |
729 return false; | |
730 | |
731 if (roleValue() == FigureRole) | |
732 return false; | |
733 | |
734 if (roleValue() == DetailsRole) | |
735 return false; | |
736 | |
737 if (roleValue() == MeterRole) | |
738 return false; | |
739 | |
740 if (roleValue() == RubyRole) | |
741 return false; | |
742 | |
743 // if this element has aria attributes on it, it should not be ignored. | |
744 if (supportsARIAAttributes()) | |
745 return false; | |
746 | |
747 // <span> tags are inline tags and not meant to convey information if they h
ave no other aria | |
748 // information on them. If we don't ignore them, they may emit signals expec
ted to come from | |
749 // their parent. In addition, because included spans are GroupRole objects,
and GroupRole | |
750 // objects are often containers with meaningful information, the inclusion o
f a span can have | |
751 // the side effect of causing the immediate parent accessible to be ignored.
This is especially | |
752 // problematic for platforms which have distinct roles for textual block ele
ments. | |
753 if (isHTMLSpanElement(node)) | |
754 return true; | |
755 | |
756 if (m_renderer->isRenderBlockFlow() && m_renderer->childrenInline() && !canS
etFocusAttribute()) | |
757 return !toRenderBlockFlow(m_renderer)->firstLineBox() && !mouseButtonLis
tener(); | |
758 | |
759 // ignore images seemingly used as spacers | |
760 if (isImage()) { | |
761 | |
762 // If the image can take focus, it should not be ignored, lest the user
not be able to interact with something important. | |
763 if (canSetFocusAttribute()) | |
764 return false; | |
765 | |
766 if (node && node->isElementNode()) { | |
767 Element* elt = toElement(node); | |
768 const AtomicString& alt = elt->getAttribute(altAttr); | |
769 // don't ignore an image that has an alt tag | |
770 if (!alt.string().containsOnlyWhitespace()) | |
771 return false; | |
772 // informal standard is to ignore images with zero-length alt string
s | |
773 if (!alt.isNull()) | |
774 return true; | |
775 } | |
776 | |
777 if (isNativeImage() && m_renderer->isImage()) { | |
778 // check for one-dimensional image | |
779 RenderImage* image = toRenderImage(m_renderer); | |
780 if (image->height() <= 1 || image->width() <= 1) | |
781 return true; | |
782 | |
783 // check whether rendered image was stretched from one-dimensional f
ile image | |
784 if (image->cachedImage()) { | |
785 LayoutSize imageSize = image->cachedImage()->imageSizeForRendere
r(m_renderer, image->view()->zoomFactor()); | |
786 return imageSize.height() <= 1 || imageSize.width() <= 1; | |
787 } | |
788 } | |
789 return false; | |
790 } | |
791 | |
792 if (isCanvas()) { | |
793 if (canvasHasFallbackContent()) | |
794 return false; | |
795 RenderHTMLCanvas* canvas = toRenderHTMLCanvas(m_renderer); | |
796 if (canvas->height() <= 1 || canvas->width() <= 1) | |
797 return true; | |
798 // Otherwise fall through; use presence of help text, title, or descript
ion to decide. | |
799 } | |
800 | |
801 if (isWebArea() || m_renderer->isListMarker()) | |
802 return false; | |
803 | |
804 // Using the help text, title or accessibility description (so we | |
805 // check if there's some kind of accessible name for the element) | |
806 // to decide an element's visibility is not as definitive as | |
807 // previous checks, so this should remain as one of the last. | |
808 // | |
809 // These checks are simplified in the interest of execution speed; | |
810 // for example, any element having an alt attribute will make it | |
811 // not ignored, rather than just images. | |
812 if (!getAttribute(aria_helpAttr).isEmpty() || !getAttribute(aria_describedby
Attr).isEmpty() || !getAttribute(altAttr).isEmpty() || !getAttribute(titleAttr).
isEmpty()) | |
813 return false; | |
814 | |
815 // Don't ignore generic focusable elements like <div tabindex=0> | |
816 // unless they're completely empty, with no children. | |
817 if (isGenericFocusableElement() && node->hasChildren()) | |
818 return false; | |
819 | |
820 if (!ariaAccessibilityDescription().isEmpty()) | |
821 return false; | |
822 | |
823 // By default, objects should be ignored so that the AX hierarchy is not | |
824 // filled with unnecessary items. | |
825 return true; | |
826 } | |
827 | |
828 // | |
829 // Properties of static elements. | |
830 // | |
831 | |
832 const AtomicString& AXRenderObject::accessKey() const | |
833 { | |
834 Node* node = m_renderer->node(); | |
835 if (!node) | |
836 return nullAtom; | |
837 if (!node->isElementNode()) | |
838 return nullAtom; | |
839 return toElement(node)->getAttribute(accesskeyAttr); | |
840 } | |
841 | |
842 AccessibilityOrientation AXRenderObject::orientation() const | |
843 { | |
844 const AtomicString& ariaOrientation = getAttribute(aria_orientationAttr); | |
845 if (equalIgnoringCase(ariaOrientation, "horizontal")) | |
846 return AccessibilityOrientationHorizontal; | |
847 if (equalIgnoringCase(ariaOrientation, "vertical")) | |
848 return AccessibilityOrientationVertical; | |
849 | |
850 return AXObject::orientation(); | |
851 } | |
852 | |
853 String AXRenderObject::text() const | |
854 { | |
855 if (isPasswordFieldAndShouldHideValue()) | |
856 return String(); | |
857 | |
858 return AXNodeObject::text(); | |
859 } | |
860 | |
861 int AXRenderObject::textLength() const | |
862 { | |
863 if (!isTextControl()) | |
864 return -1; | |
865 | |
866 if (isPasswordFieldAndShouldHideValue()) | |
867 return -1; // need to return something distinct from 0 | |
868 | |
869 return text().length(); | |
870 } | |
871 | |
872 KURL AXRenderObject::url() const | |
873 { | |
874 if (isAnchor() && isHTMLAnchorElement(m_renderer->node())) { | |
875 if (HTMLAnchorElement* anchor = toHTMLAnchorElement(anchorElement())) | |
876 return anchor->href(); | |
877 } | |
878 | |
879 if (isWebArea()) | |
880 return m_renderer->document().url(); | |
881 | |
882 if (isImage() && isHTMLImageElement(m_renderer->node())) | |
883 return toHTMLImageElement(*m_renderer->node()).src(); | |
884 | |
885 if (isInputImage()) | |
886 return toHTMLInputElement(m_renderer->node())->src(); | |
887 | |
888 return KURL(); | |
889 } | |
890 | |
891 // | |
892 // Properties of interactive elements. | |
893 // | |
894 | |
895 static String queryString(WebLocalizedString::Name name) | |
896 { | |
897 return Locale::defaultLocale().queryString(name); | |
898 } | |
899 | |
900 String AXRenderObject::actionVerb() const | |
901 { | |
902 switch (roleValue()) { | |
903 case ButtonRole: | |
904 case ToggleButtonRole: | |
905 return queryString(WebLocalizedString::AXButtonActionVerb); | |
906 case TextFieldRole: | |
907 case TextAreaRole: | |
908 return queryString(WebLocalizedString::AXTextFieldActionVerb); | |
909 case RadioButtonRole: | |
910 return queryString(WebLocalizedString::AXRadioButtonActionVerb); | |
911 case CheckBoxRole: | |
912 return queryString(isChecked() ? WebLocalizedString::AXCheckedCheckBoxAc
tionVerb : WebLocalizedString::AXUncheckedCheckBoxActionVerb); | |
913 case LinkRole: | |
914 return queryString(WebLocalizedString::AXLinkActionVerb); | |
915 default: | |
916 return emptyString(); | |
917 } | |
918 } | |
919 | |
920 String AXRenderObject::stringValue() const | |
921 { | |
922 if (!m_renderer) | |
923 return String(); | |
924 | |
925 if (isPasswordFieldAndShouldHideValue()) | |
926 return String(); | |
927 | |
928 RenderBoxModelObject* cssBox = renderBoxModelObject(); | |
929 | |
930 if (ariaRoleAttribute() == StaticTextRole) { | |
931 String staticText = text(); | |
932 if (!staticText.length()) | |
933 staticText = textUnderElement(); | |
934 return staticText; | |
935 } | |
936 | |
937 if (m_renderer->isText()) | |
938 return textUnderElement(); | |
939 | |
940 if (cssBox && cssBox->isMenuList()) { | |
941 // RenderMenuList will go straight to the text() of its selected item. | |
942 // This has to be overridden in the case where the selected item has an
ARIA label. | |
943 HTMLSelectElement* selectElement = toHTMLSelectElement(m_renderer->node(
)); | |
944 int selectedIndex = selectElement->selectedIndex(); | |
945 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = se
lectElement->listItems(); | |
946 if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems
.size()) { | |
947 const AtomicString& overriddenDescription = listItems[selectedIndex]
->fastGetAttribute(aria_labelAttr); | |
948 if (!overriddenDescription.isNull()) | |
949 return overriddenDescription; | |
950 } | |
951 return toRenderMenuList(m_renderer)->text(); | |
952 } | |
953 | |
954 if (m_renderer->isListMarker()) | |
955 return toRenderListMarker(m_renderer)->text(); | |
956 | |
957 if (isWebArea()) { | |
958 // FIXME: Why would a renderer exist when the Document isn't attached to
a frame? | |
959 if (m_renderer->frame()) | |
960 return String(); | |
961 | |
962 ASSERT_NOT_REACHED(); | |
963 } | |
964 | |
965 if (isTextControl()) | |
966 return text(); | |
967 | |
968 if (m_renderer->isFileUploadControl()) | |
969 return toRenderFileUploadControl(m_renderer)->fileTextValue(); | |
970 | |
971 // FIXME: We might need to implement a value here for more types | |
972 // FIXME: It would be better not to advertise a value at all for the types f
or which we don't implement one; | |
973 // this would require subclassing or making accessibilityAttributeNames do s
omething other than return a | |
974 // single static array. | |
975 return String(); | |
976 } | |
977 | |
978 // | |
979 // ARIA attributes. | |
980 // | |
981 | |
982 AXObject* AXRenderObject::activeDescendant() const | |
983 { | |
984 if (!m_renderer) | |
985 return 0; | |
986 | |
987 if (m_renderer->node() && !m_renderer->node()->isElementNode()) | |
988 return 0; | |
989 | |
990 Element* element = toElement(m_renderer->node()); | |
991 if (!element) | |
992 return 0; | |
993 | |
994 const AtomicString& activeDescendantAttrStr = element->getAttribute(aria_act
ivedescendantAttr); | |
995 if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) | |
996 return 0; | |
997 | |
998 Element* target = element->treeScope().getElementById(activeDescendantAttrSt
r); | |
999 if (!target) | |
1000 return 0; | |
1001 | |
1002 AXObject* obj = axObjectCache()->getOrCreate(target); | |
1003 | |
1004 // An activedescendant is only useful if it has a renderer, because that's w
hat's needed to post the notification. | |
1005 if (obj && obj->isAXRenderObject()) | |
1006 return obj; | |
1007 | |
1008 return 0; | |
1009 } | |
1010 | |
1011 void AXRenderObject::accessibilityChildrenFromAttribute(QualifiedName attr, Acce
ssibilityChildrenVector& children) const | |
1012 { | |
1013 WillBeHeapVector<RawPtrWillBeMember<Element> > elements; | |
1014 elementsFromAttribute(elements, attr); | |
1015 | |
1016 AXObjectCacheImpl* cache = axObjectCache(); | |
1017 unsigned count = elements.size(); | |
1018 for (unsigned k = 0; k < count; ++k) { | |
1019 Element* element = elements[k]; | |
1020 AXObject* child = cache->getOrCreate(element); | |
1021 if (child) | |
1022 children.append(child); | |
1023 } | |
1024 } | |
1025 | |
1026 void AXRenderObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) con
st | |
1027 { | |
1028 accessibilityChildrenFromAttribute(aria_flowtoAttr, flowTo); | |
1029 } | |
1030 | |
1031 void AXRenderObject::ariaControlsElements(AccessibilityChildrenVector& controls)
const | |
1032 { | |
1033 accessibilityChildrenFromAttribute(aria_controlsAttr, controls); | |
1034 } | |
1035 | |
1036 void AXRenderObject::ariaDescribedbyElements(AccessibilityChildrenVector& descri
bedby) const | |
1037 { | |
1038 accessibilityChildrenFromAttribute(aria_describedbyAttr, describedby); | |
1039 } | |
1040 | |
1041 void AXRenderObject::ariaLabelledbyElements(AccessibilityChildrenVector& labelle
dby) const | |
1042 { | |
1043 accessibilityChildrenFromAttribute(aria_labelledbyAttr, labelledby); | |
1044 } | |
1045 | |
1046 void AXRenderObject::ariaOwnsElements(AccessibilityChildrenVector& owns) const | |
1047 { | |
1048 accessibilityChildrenFromAttribute(aria_ownsAttr, owns); | |
1049 } | |
1050 | |
1051 bool AXRenderObject::ariaHasPopup() const | |
1052 { | |
1053 return elementAttributeValue(aria_haspopupAttr); | |
1054 } | |
1055 | |
1056 bool AXRenderObject::ariaRoleHasPresentationalChildren() const | |
1057 { | |
1058 switch (m_ariaRole) { | |
1059 case ButtonRole: | |
1060 case SliderRole: | |
1061 case ImageRole: | |
1062 case ProgressIndicatorRole: | |
1063 case SpinButtonRole: | |
1064 // case SeparatorRole: | |
1065 return true; | |
1066 default: | |
1067 return false; | |
1068 } | |
1069 } | |
1070 | |
1071 bool AXRenderObject::isPresentationalChildOfAriaRole() const | |
1072 { | |
1073 // Walk the parent chain looking for a parent that has presentational childr
en | |
1074 AXObject* parent; | |
1075 for (parent = parentObject(); parent && !parent->ariaRoleHasPresentationalCh
ildren(); parent = parent->parentObject()) | |
1076 { } | |
1077 | |
1078 return parent; | |
1079 } | |
1080 | |
1081 bool AXRenderObject::shouldFocusActiveDescendant() const | |
1082 { | |
1083 switch (ariaRoleAttribute()) { | |
1084 case ComboBoxRole: | |
1085 case GridRole: | |
1086 case GroupRole: | |
1087 case ListBoxRole: | |
1088 case MenuRole: | |
1089 case MenuBarRole: | |
1090 case OutlineRole: | |
1091 case PopUpButtonRole: | |
1092 case ProgressIndicatorRole: | |
1093 case RadioGroupRole: | |
1094 case RowRole: | |
1095 case TabListRole: | |
1096 case ToolbarRole: | |
1097 case TreeRole: | |
1098 case TreeGridRole: | |
1099 return true; | |
1100 default: | |
1101 return false; | |
1102 } | |
1103 } | |
1104 | |
1105 bool AXRenderObject::supportsARIADragging() const | |
1106 { | |
1107 const AtomicString& grabbed = getAttribute(aria_grabbedAttr); | |
1108 return equalIgnoringCase(grabbed, "true") || equalIgnoringCase(grabbed, "fal
se"); | |
1109 } | |
1110 | |
1111 bool AXRenderObject::supportsARIADropping() const | |
1112 { | |
1113 const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr); | |
1114 return !dropEffect.isEmpty(); | |
1115 } | |
1116 | |
1117 bool AXRenderObject::supportsARIAFlowTo() const | |
1118 { | |
1119 return !getAttribute(aria_flowtoAttr).isEmpty(); | |
1120 } | |
1121 | |
1122 bool AXRenderObject::supportsARIAOwns() const | |
1123 { | |
1124 if (!m_renderer) | |
1125 return false; | |
1126 const AtomicString& ariaOwns = getAttribute(aria_ownsAttr); | |
1127 | |
1128 return !ariaOwns.isEmpty(); | |
1129 } | |
1130 | |
1131 // | |
1132 // ARIA live-region features. | |
1133 // | |
1134 | |
1135 const AtomicString& AXRenderObject::liveRegionStatus() const | |
1136 { | |
1137 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive, ("asserti
ve", AtomicString::ConstructFromLiteral)); | |
1138 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite", A
tomicString::ConstructFromLiteral)); | |
1139 DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off", AtomicS
tring::ConstructFromLiteral)); | |
1140 | |
1141 const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr); | |
1142 // These roles have implicit live region status. | |
1143 if (liveRegionStatus.isEmpty()) { | |
1144 switch (roleValue()) { | |
1145 case AlertDialogRole: | |
1146 case AlertRole: | |
1147 return liveRegionStatusAssertive; | |
1148 case LogRole: | |
1149 case StatusRole: | |
1150 return liveRegionStatusPolite; | |
1151 case TimerRole: | |
1152 case MarqueeRole: | |
1153 return liveRegionStatusOff; | |
1154 default: | |
1155 break; | |
1156 } | |
1157 } | |
1158 | |
1159 return liveRegionStatus; | |
1160 } | |
1161 | |
1162 const AtomicString& AXRenderObject::liveRegionRelevant() const | |
1163 { | |
1164 DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant, ("additio
ns text", AtomicString::ConstructFromLiteral)); | |
1165 const AtomicString& relevant = getAttribute(aria_relevantAttr); | |
1166 | |
1167 // Default aria-relevant = "additions text". | |
1168 if (relevant.isEmpty()) | |
1169 return defaultLiveRegionRelevant; | |
1170 | |
1171 return relevant; | |
1172 } | |
1173 | |
1174 bool AXRenderObject::liveRegionAtomic() const | |
1175 { | |
1176 return elementAttributeValue(aria_atomicAttr); | |
1177 } | |
1178 | |
1179 bool AXRenderObject::liveRegionBusy() const | |
1180 { | |
1181 return elementAttributeValue(aria_busyAttr); | |
1182 } | |
1183 | |
1184 // | |
1185 // Accessibility Text. | |
1186 // | |
1187 | |
1188 String AXRenderObject::textUnderElement() const | |
1189 { | |
1190 if (!m_renderer) | |
1191 return String(); | |
1192 | |
1193 if (m_renderer->isFileUploadControl()) | |
1194 return toRenderFileUploadControl(m_renderer)->buttonValue(); | |
1195 | |
1196 if (m_renderer->isText()) | |
1197 return toRenderText(m_renderer)->plainText(); | |
1198 | |
1199 return AXNodeObject::textUnderElement(); | |
1200 } | |
1201 | |
1202 // | |
1203 // Accessibility Text - (To be deprecated). | |
1204 // | |
1205 | |
1206 String AXRenderObject::helpText() const | |
1207 { | |
1208 if (!m_renderer) | |
1209 return String(); | |
1210 | |
1211 const AtomicString& ariaHelp = getAttribute(aria_helpAttr); | |
1212 if (!ariaHelp.isEmpty()) | |
1213 return ariaHelp; | |
1214 | |
1215 String describedBy = ariaDescribedByAttribute(); | |
1216 if (!describedBy.isEmpty()) | |
1217 return describedBy; | |
1218 | |
1219 String description = accessibilityDescription(); | |
1220 for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { | |
1221 if (curr->node() && curr->node()->isHTMLElement()) { | |
1222 const AtomicString& summary = toElement(curr->node())->getAttribute(
summaryAttr); | |
1223 if (!summary.isEmpty()) | |
1224 return summary; | |
1225 | |
1226 // The title attribute should be used as help text unless it is alre
ady being used as descriptive text. | |
1227 const AtomicString& title = toElement(curr->node())->getAttribute(ti
tleAttr); | |
1228 if (!title.isEmpty() && description != title) | |
1229 return title; | |
1230 } | |
1231 | |
1232 // Only take help text from an ancestor element if its a group or an unk
nown role. If help was | |
1233 // added to those kinds of elements, it is likely it was meant for a chi
ld element. | |
1234 AXObject* axObj = axObjectCache()->getOrCreate(curr); | |
1235 if (axObj) { | |
1236 AccessibilityRole role = axObj->roleValue(); | |
1237 if (role != GroupRole && role != UnknownRole) | |
1238 break; | |
1239 } | |
1240 } | |
1241 | |
1242 return String(); | |
1243 } | |
1244 | |
1245 // | |
1246 // Position and size. | |
1247 // | |
1248 | |
1249 void AXRenderObject::checkCachedElementRect() const | |
1250 { | |
1251 if (m_cachedElementRectDirty) | |
1252 return; | |
1253 | |
1254 if (!m_renderer) | |
1255 return; | |
1256 | |
1257 if (!m_renderer->isBox()) | |
1258 return; | |
1259 | |
1260 bool dirty = false; | |
1261 RenderBox* box = toRenderBox(m_renderer); | |
1262 if (box->frameRect() != m_cachedFrameRect) | |
1263 dirty = true; | |
1264 | |
1265 if (box->canBeScrolledAndHasScrollableArea()) { | |
1266 ScrollableArea* scrollableArea = box->scrollableArea(); | |
1267 if (scrollableArea && scrollableArea->scrollPosition() != m_cachedScroll
Position) | |
1268 dirty = true; | |
1269 } | |
1270 | |
1271 if (dirty) | |
1272 markCachedElementRectDirty(); | |
1273 } | |
1274 | |
1275 void AXRenderObject::updateCachedElementRect() const | |
1276 { | |
1277 if (!m_cachedElementRectDirty) | |
1278 return; | |
1279 | |
1280 if (!m_renderer) | |
1281 return; | |
1282 | |
1283 if (!m_renderer->isBox()) | |
1284 return; | |
1285 | |
1286 RenderBox* box = toRenderBox(m_renderer); | |
1287 m_cachedFrameRect = box->frameRect(); | |
1288 | |
1289 if (box->canBeScrolledAndHasScrollableArea()) { | |
1290 ScrollableArea* scrollableArea = box->scrollableArea(); | |
1291 if (scrollableArea) | |
1292 m_cachedScrollPosition = scrollableArea->scrollPosition(); | |
1293 } | |
1294 | |
1295 m_cachedElementRect = computeElementRect(); | |
1296 m_cachedElementRectDirty = false; | |
1297 } | |
1298 | |
1299 void AXRenderObject::markCachedElementRectDirty() const | |
1300 { | |
1301 if (m_cachedElementRectDirty) | |
1302 return; | |
1303 | |
1304 // Marks children recursively, if this element changed. | |
1305 m_cachedElementRectDirty = true; | |
1306 for (AXObject* child = firstChild(); child; child = child->nextSibling()) | |
1307 child->markCachedElementRectDirty(); | |
1308 } | |
1309 | |
1310 IntPoint AXRenderObject::clickPoint() | |
1311 { | |
1312 // Headings are usually much wider than their textual content. If the mid po
int is used, often it can be wrong. | |
1313 if (isHeading() && children().size() == 1) | |
1314 return children()[0]->clickPoint(); | |
1315 | |
1316 // use the default position unless this is an editable web area, in which ca
se we use the selection bounds. | |
1317 if (!isWebArea() || isReadOnly()) | |
1318 return AXObject::clickPoint(); | |
1319 | |
1320 IntRect bounds = pixelSnappedIntRect(elementRect()); | |
1321 return IntPoint(bounds.x() + (bounds.width() / 2), bounds.y() - (bounds.heig
ht() / 2)); | |
1322 } | |
1323 | |
1324 // | |
1325 // Hit testing. | |
1326 // | |
1327 | |
1328 AXObject* AXRenderObject::accessibilityHitTest(const IntPoint& point) const | |
1329 { | |
1330 if (!m_renderer || !m_renderer->hasLayer()) | |
1331 return 0; | |
1332 | |
1333 RenderLayer* layer = toRenderBox(m_renderer)->layer(); | |
1334 | |
1335 HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active); | |
1336 HitTestResult hitTestResult = HitTestResult(point); | |
1337 layer->hitTest(request, hitTestResult); | |
1338 if (!hitTestResult.innerNode()) | |
1339 return 0; | |
1340 | |
1341 Node* node = hitTestResult.innerNode(); | |
1342 | |
1343 // Allow the hit test to return media control buttons. | |
1344 if (node->isInShadowTree() && (!isHTMLInputElement(*node) || !node->isMediaC
ontrolElement())) | |
1345 node = node->shadowHost(); | |
1346 | |
1347 if (isHTMLAreaElement(node)) | |
1348 return accessibilityImageMapHitTest(toHTMLAreaElement(node), point); | |
1349 | |
1350 if (isHTMLOptionElement(node)) | |
1351 node = toHTMLOptionElement(*node).ownerSelectElement(); | |
1352 | |
1353 RenderObject* obj = node->renderer(); | |
1354 if (!obj) | |
1355 return 0; | |
1356 | |
1357 AXObject* result = toAXObjectCacheImpl(obj->document().axObjectCache())->get
OrCreate(obj); | |
1358 result->updateChildrenIfNecessary(); | |
1359 | |
1360 // Allow the element to perform any hit-testing it might need to do to reach
non-render children. | |
1361 result = result->elementAccessibilityHitTest(point); | |
1362 | |
1363 if (result && result->accessibilityIsIgnored()) { | |
1364 // If this element is the label of a control, a hit test should return t
he control. | |
1365 if (result->isAXRenderObject()) { | |
1366 AXObject* controlObject = toAXRenderObject(result)->correspondingCon
trolForLabelElement(); | |
1367 if (controlObject && !controlObject->exposesTitleUIElement()) | |
1368 return controlObject; | |
1369 } | |
1370 | |
1371 result = result->parentObjectUnignored(); | |
1372 } | |
1373 | |
1374 return result; | |
1375 } | |
1376 | |
1377 AXObject* AXRenderObject::elementAccessibilityHitTest(const IntPoint& point) con
st | |
1378 { | |
1379 if (isSVGImage()) | |
1380 return remoteSVGElementHitTest(point); | |
1381 | |
1382 return AXObject::elementAccessibilityHitTest(point); | |
1383 } | |
1384 | |
1385 // | |
1386 // High-level accessibility tree access. | |
1387 // | |
1388 | |
1389 AXObject* AXRenderObject::computeParent() const | |
1390 { | |
1391 if (!m_renderer) | |
1392 return 0; | |
1393 | |
1394 if (ariaRoleAttribute() == MenuBarRole) | |
1395 return axObjectCache()->getOrCreate(m_renderer->parent()); | |
1396 | |
1397 // menuButton and its corresponding menu are DOM siblings, but Accessibility
needs them to be parent/child | |
1398 if (ariaRoleAttribute() == MenuRole) { | |
1399 AXObject* parent = menuButtonForMenu(); | |
1400 if (parent) | |
1401 return parent; | |
1402 } | |
1403 | |
1404 RenderObject* parentObj = renderParentObject(); | |
1405 if (parentObj) | |
1406 return axObjectCache()->getOrCreate(parentObj); | |
1407 | |
1408 // WebArea's parent should be the scroll view containing it. | |
1409 if (isWebArea()) | |
1410 return axObjectCache()->getOrCreate(m_renderer->frame()->view()); | |
1411 | |
1412 return 0; | |
1413 } | |
1414 | |
1415 AXObject* AXRenderObject::computeParentIfExists() const | |
1416 { | |
1417 if (!m_renderer) | |
1418 return 0; | |
1419 | |
1420 if (ariaRoleAttribute() == MenuBarRole) | |
1421 return axObjectCache()->get(m_renderer->parent()); | |
1422 | |
1423 // menuButton and its corresponding menu are DOM siblings, but Accessibility
needs them to be parent/child | |
1424 if (ariaRoleAttribute() == MenuRole) { | |
1425 AXObject* parent = menuButtonForMenu(); | |
1426 if (parent) | |
1427 return parent; | |
1428 } | |
1429 | |
1430 RenderObject* parentObj = renderParentObject(); | |
1431 if (parentObj) | |
1432 return axObjectCache()->get(parentObj); | |
1433 | |
1434 // WebArea's parent should be the scroll view containing it. | |
1435 if (isWebArea()) | |
1436 return axObjectCache()->get(m_renderer->frame()->view()); | |
1437 | |
1438 return 0; | |
1439 } | |
1440 | |
1441 // | |
1442 // Low-level accessibility tree exploration, only for use within the accessibili
ty module. | |
1443 // | |
1444 | |
1445 AXObject* AXRenderObject::firstChild() const | |
1446 { | |
1447 if (!m_renderer) | |
1448 return 0; | |
1449 | |
1450 RenderObject* firstChild = firstChildConsideringContinuation(m_renderer); | |
1451 | |
1452 if (!firstChild) | |
1453 return 0; | |
1454 | |
1455 return axObjectCache()->getOrCreate(firstChild); | |
1456 } | |
1457 | |
1458 AXObject* AXRenderObject::nextSibling() const | |
1459 { | |
1460 if (!m_renderer) | |
1461 return 0; | |
1462 | |
1463 RenderObject* nextSibling = 0; | |
1464 | |
1465 RenderInline* inlineContinuation = m_renderer->isRenderBlock() ? toRenderBlo
ck(m_renderer)->inlineElementContinuation() : 0; | |
1466 if (inlineContinuation) { | |
1467 // Case 1: node is a block and has an inline continuation. Next sibling
is the inline continuation's first child. | |
1468 nextSibling = firstChildConsideringContinuation(inlineContinuation); | |
1469 } else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(m_rend
erer)) { | |
1470 // Case 2: Anonymous block parent of the start of a continuation - skip
all the way to | |
1471 // after the parent of the end, since everything in between will be link
ed up via the continuation. | |
1472 RenderObject* lastParent = endOfContinuations(toRenderBlock(m_renderer)-
>lastChild())->parent(); | |
1473 while (lastChildHasContinuation(lastParent)) | |
1474 lastParent = endOfContinuations(lastParent->slowLastChild())->parent
(); | |
1475 nextSibling = lastParent->nextSibling(); | |
1476 } else if (RenderObject* ns = m_renderer->nextSibling()) { | |
1477 // Case 3: node has an actual next sibling | |
1478 nextSibling = ns; | |
1479 } else if (isInlineWithContinuation(m_renderer)) { | |
1480 // Case 4: node is an inline with a continuation. Next sibling is the ne
xt sibling of the end | |
1481 // of the continuation chain. | |
1482 nextSibling = endOfContinuations(m_renderer)->nextSibling(); | |
1483 } else if (isInlineWithContinuation(m_renderer->parent())) { | |
1484 // Case 5: node has no next sibling, and its parent is an inline with a
continuation. | |
1485 RenderObject* continuation = toRenderInline(m_renderer->parent())->conti
nuation(); | |
1486 | |
1487 if (continuation->isRenderBlock()) { | |
1488 // Case 5a: continuation is a block - in this case the block itself
is the next sibling. | |
1489 nextSibling = continuation; | |
1490 } else { | |
1491 // Case 5b: continuation is an inline - in this case the inline's fi
rst child is the next sibling. | |
1492 nextSibling = firstChildConsideringContinuation(continuation); | |
1493 } | |
1494 } | |
1495 | |
1496 if (!nextSibling) | |
1497 return 0; | |
1498 | |
1499 return axObjectCache()->getOrCreate(nextSibling); | |
1500 } | |
1501 | |
1502 void AXRenderObject::addChildren() | |
1503 { | |
1504 // If the need to add more children in addition to existing children arises, | |
1505 // childrenChanged should have been called, leaving the object with no child
ren. | |
1506 ASSERT(!m_haveChildren); | |
1507 | |
1508 m_haveChildren = true; | |
1509 | |
1510 if (!canHaveChildren()) | |
1511 return; | |
1512 | |
1513 for (RefPtr<AXObject> obj = firstChild(); obj; obj = obj->nextSibling()) | |
1514 addChild(obj.get()); | |
1515 | |
1516 addHiddenChildren(); | |
1517 addAttachmentChildren(); | |
1518 addPopupChildren(); | |
1519 addImageMapChildren(); | |
1520 addTextFieldChildren(); | |
1521 addCanvasChildren(); | |
1522 addRemoteSVGChildren(); | |
1523 addInlineTextBoxChildren(); | |
1524 | |
1525 for (unsigned i = 0; i < m_children.size(); ++i) { | |
1526 if (!m_children[i].get()->cachedParentObject()) | |
1527 m_children[i].get()->setParent(this); | |
1528 } | |
1529 } | |
1530 | |
1531 bool AXRenderObject::canHaveChildren() const | |
1532 { | |
1533 if (!m_renderer) | |
1534 return false; | |
1535 | |
1536 return AXNodeObject::canHaveChildren(); | |
1537 } | |
1538 | |
1539 void AXRenderObject::updateChildrenIfNecessary() | |
1540 { | |
1541 if (needsToUpdateChildren()) | |
1542 clearChildren(); | |
1543 | |
1544 AXObject::updateChildrenIfNecessary(); | |
1545 } | |
1546 | |
1547 void AXRenderObject::clearChildren() | |
1548 { | |
1549 AXObject::clearChildren(); | |
1550 m_childrenDirty = false; | |
1551 } | |
1552 | |
1553 AXObject* AXRenderObject::observableObject() const | |
1554 { | |
1555 // Find the object going up the parent chain that is used in accessibility t
o monitor certain notifications. | |
1556 for (RenderObject* renderer = m_renderer; renderer && renderer->node(); rend
erer = renderer->parent()) { | |
1557 if (renderObjectIsObservable(renderer)) | |
1558 return axObjectCache()->getOrCreate(renderer); | |
1559 } | |
1560 | |
1561 return 0; | |
1562 } | |
1563 | |
1564 // | |
1565 // Properties of the object's owning document or page. | |
1566 // | |
1567 | |
1568 double AXRenderObject::estimatedLoadingProgress() const | |
1569 { | |
1570 if (!m_renderer) | |
1571 return 0; | |
1572 | |
1573 if (isLoaded()) | |
1574 return 1.0; | |
1575 | |
1576 if (LocalFrame* frame = m_renderer->document().frame()) | |
1577 return frame->loader().progress().estimatedProgress(); | |
1578 return 0; | |
1579 } | |
1580 | |
1581 // | |
1582 // DOM and Render tree access. | |
1583 // | |
1584 | |
1585 Node* AXRenderObject::node() const | |
1586 { | |
1587 return m_renderer ? m_renderer->node() : 0; | |
1588 } | |
1589 | |
1590 Document* AXRenderObject::document() const | |
1591 { | |
1592 if (!m_renderer) | |
1593 return 0; | |
1594 return &m_renderer->document(); | |
1595 } | |
1596 | |
1597 FrameView* AXRenderObject::documentFrameView() const | |
1598 { | |
1599 if (!m_renderer) | |
1600 return 0; | |
1601 | |
1602 // this is the RenderObject's Document's LocalFrame's FrameView | |
1603 return m_renderer->document().view(); | |
1604 } | |
1605 | |
1606 Element* AXRenderObject::anchorElement() const | |
1607 { | |
1608 if (!m_renderer) | |
1609 return 0; | |
1610 | |
1611 AXObjectCacheImpl* cache = axObjectCache(); | |
1612 RenderObject* currRenderer; | |
1613 | |
1614 // Search up the render tree for a RenderObject with a DOM node. Defer to an
earlier continuation, though. | |
1615 for (currRenderer = m_renderer; currRenderer && !currRenderer->node(); currR
enderer = currRenderer->parent()) { | |
1616 if (currRenderer->isAnonymousBlock()) { | |
1617 RenderObject* continuation = toRenderBlock(currRenderer)->continuati
on(); | |
1618 if (continuation) | |
1619 return cache->getOrCreate(continuation)->anchorElement(); | |
1620 } | |
1621 } | |
1622 | |
1623 // bail if none found | |
1624 if (!currRenderer) | |
1625 return 0; | |
1626 | |
1627 // search up the DOM tree for an anchor element | |
1628 // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElem
ent | |
1629 Node* node = currRenderer->node(); | |
1630 for ( ; node; node = node->parentNode()) { | |
1631 if (isHTMLAnchorElement(*node) || (node->renderer() && cache->getOrCreat
e(node->renderer())->isAnchor())) | |
1632 return toElement(node); | |
1633 } | |
1634 | |
1635 return 0; | |
1636 } | |
1637 | |
1638 Widget* AXRenderObject::widgetForAttachmentView() const | |
1639 { | |
1640 if (!isAttachment()) | |
1641 return 0; | |
1642 return toRenderPart(m_renderer)->widget(); | |
1643 } | |
1644 | |
1645 // | |
1646 // Selected text. | |
1647 // | |
1648 | |
1649 AXObject::PlainTextRange AXRenderObject::selectedTextRange() const | |
1650 { | |
1651 if (!isTextControl()) | |
1652 return PlainTextRange(); | |
1653 | |
1654 if (isPasswordFieldAndShouldHideValue()) | |
1655 return PlainTextRange(); | |
1656 | |
1657 AccessibilityRole ariaRole = ariaRoleAttribute(); | |
1658 if (isNativeTextControl() && ariaRole == UnknownRole && m_renderer->isTextCo
ntrol()) { | |
1659 HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer
)->textFormControlElement(); | |
1660 return PlainTextRange(textControl->selectionStart(), textControl->select
ionEnd() - textControl->selectionStart()); | |
1661 } | |
1662 | |
1663 if (ariaRole == UnknownRole) | |
1664 return PlainTextRange(); | |
1665 | |
1666 return ariaSelectedTextRange(); | |
1667 } | |
1668 | |
1669 VisibleSelection AXRenderObject::selection() const | |
1670 { | |
1671 return m_renderer->frame()->selection().selection(); | |
1672 } | |
1673 | |
1674 // | |
1675 // Modify or take an action on an object. | |
1676 // | |
1677 | |
1678 void AXRenderObject::setSelectedTextRange(const PlainTextRange& range) | |
1679 { | |
1680 if (isNativeTextControl() && m_renderer->isTextControl()) { | |
1681 HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer
)->textFormControlElement(); | |
1682 textControl->setSelectionRange(range.start, range.start + range.length); | |
1683 return; | |
1684 } | |
1685 | |
1686 Document& document = m_renderer->document(); | |
1687 LocalFrame* frame = document.frame(); | |
1688 if (!frame) | |
1689 return; | |
1690 Node* node = m_renderer->node(); | |
1691 frame->selection().setSelection(VisibleSelection(Position(node, range.start,
Position::PositionIsOffsetInAnchor), | |
1692 Position(node, range.start + range.length, Position::PositionIsOffsetInA
nchor), DOWNSTREAM)); | |
1693 } | |
1694 | |
1695 void AXRenderObject::setValue(const String& string) | |
1696 { | |
1697 if (!node() || !node()->isElementNode()) | |
1698 return; | |
1699 if (!m_renderer || !m_renderer->isBoxModelObject()) | |
1700 return; | |
1701 | |
1702 RenderBoxModelObject* renderer = toRenderBoxModelObject(m_renderer); | |
1703 if (renderer->isTextField() && isHTMLInputElement(*node())) | |
1704 toHTMLInputElement(*node()).setValue(string); | |
1705 else if (renderer->isTextArea() && isHTMLTextAreaElement(*node())) | |
1706 toHTMLTextAreaElement(*node()).setValue(string); | |
1707 } | |
1708 | |
1709 // FIXME: This function should use an IntSize to avoid the conversion below. | |
1710 void AXRenderObject::scrollTo(const IntPoint& point) const | |
1711 { | |
1712 if (!m_renderer || !m_renderer->isBox()) | |
1713 return; | |
1714 | |
1715 RenderBox* box = toRenderBox(m_renderer); | |
1716 if (!box->canBeScrolledAndHasScrollableArea()) | |
1717 return; | |
1718 | |
1719 box->scrollToOffset(IntSize(point.x(), point.y())); | |
1720 } | |
1721 | |
1722 // | |
1723 // Notifications that this object may have changed. | |
1724 // | |
1725 | |
1726 void AXRenderObject::handleActiveDescendantChanged() | |
1727 { | |
1728 Element* element = toElement(renderer()->node()); | |
1729 if (!element) | |
1730 return; | |
1731 Document& doc = renderer()->document(); | |
1732 if (!doc.frame()->selection().isFocusedAndActive() || doc.focusedElement() !
= element) | |
1733 return; | |
1734 AXRenderObject* activedescendant = toAXRenderObject(activeDescendant()); | |
1735 | |
1736 if (activedescendant && shouldNotifyActiveDescendant()) | |
1737 toAXObjectCacheImpl(doc.axObjectCache())->postNotification(m_renderer, A
XObjectCacheImpl::AXActiveDescendantChanged, true); | |
1738 } | |
1739 | |
1740 void AXRenderObject::handleAriaExpandedChanged() | |
1741 { | |
1742 // Find if a parent of this object should handle aria-expanded changes. | |
1743 AXObject* containerParent = this->parentObject(); | |
1744 while (containerParent) { | |
1745 bool foundParent = false; | |
1746 | |
1747 switch (containerParent->roleValue()) { | |
1748 case TreeRole: | |
1749 case TreeGridRole: | |
1750 case GridRole: | |
1751 case TableRole: | |
1752 case BrowserRole: | |
1753 foundParent = true; | |
1754 break; | |
1755 default: | |
1756 break; | |
1757 } | |
1758 | |
1759 if (foundParent) | |
1760 break; | |
1761 | |
1762 containerParent = containerParent->parentObject(); | |
1763 } | |
1764 | |
1765 // Post that the row count changed. | |
1766 if (containerParent) | |
1767 axObjectCache()->postNotification(containerParent, document(), AXObjectC
acheImpl::AXRowCountChanged, true); | |
1768 | |
1769 // Post that the specific row either collapsed or expanded. | |
1770 AccessibilityExpanded expanded = isExpanded(); | |
1771 if (!expanded) | |
1772 return; | |
1773 | |
1774 if (roleValue() == RowRole || roleValue() == TreeItemRole) { | |
1775 AXObjectCacheImpl::AXNotification notification = AXObjectCacheImpl::AXRo
wExpanded; | |
1776 if (expanded == ExpandedCollapsed) | |
1777 notification = AXObjectCacheImpl::AXRowCollapsed; | |
1778 | |
1779 axObjectCache()->postNotification(this, document(), notification, true); | |
1780 } | |
1781 } | |
1782 | |
1783 void AXRenderObject::textChanged() | |
1784 { | |
1785 if (!m_renderer) | |
1786 return; | |
1787 | |
1788 Settings* settings = document()->settings(); | |
1789 if (settings && settings->inlineTextBoxAccessibilityEnabled() && roleValue()
== StaticTextRole) | |
1790 childrenChanged(); | |
1791 | |
1792 // Do this last - AXNodeObject::textChanged posts live region announcements, | |
1793 // and we should update the inline text boxes first. | |
1794 AXNodeObject::textChanged(); | |
1795 } | |
1796 | |
1797 // | |
1798 // Text metrics. Most of these should be deprecated, needs major cleanup. | |
1799 // | |
1800 | |
1801 // NOTE: Consider providing this utility method as AX API | |
1802 int AXRenderObject::index(const VisiblePosition& position) const | |
1803 { | |
1804 if (position.isNull() || !isTextControl()) | |
1805 return -1; | |
1806 | |
1807 if (renderObjectContainsPosition(m_renderer, position.deepEquivalent())) | |
1808 return indexForVisiblePosition(position); | |
1809 | |
1810 return -1; | |
1811 } | |
1812 | |
1813 VisiblePosition AXRenderObject::visiblePositionForIndex(int index) const | |
1814 { | |
1815 if (!m_renderer) | |
1816 return VisiblePosition(); | |
1817 | |
1818 if (isNativeTextControl() && m_renderer->isTextControl()) | |
1819 return toRenderTextControl(m_renderer)->textFormControlElement()->visibl
ePositionForIndex(index); | |
1820 | |
1821 if (!allowsTextRanges() && !m_renderer->isText()) | |
1822 return VisiblePosition(); | |
1823 | |
1824 Node* node = m_renderer->node(); | |
1825 if (!node) | |
1826 return VisiblePosition(); | |
1827 | |
1828 if (index <= 0) | |
1829 return VisiblePosition(firstPositionInOrBeforeNode(node), DOWNSTREAM); | |
1830 | |
1831 Position start, end; | |
1832 bool selected = Range::selectNodeContents(node, start, end); | |
1833 if (!selected) | |
1834 return VisiblePosition(); | |
1835 | |
1836 CharacterIterator it(start, end); | |
1837 it.advance(index - 1); | |
1838 return VisiblePosition(Position(it.endContainer(), it.endOffset(), Position:
:PositionIsOffsetInAnchor), UPSTREAM); | |
1839 } | |
1840 | |
1841 int AXRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const | |
1842 { | |
1843 if (isNativeTextControl() && m_renderer->isTextControl()) { | |
1844 HTMLTextFormControlElement* textControl = toRenderTextControl(m_renderer
)->textFormControlElement(); | |
1845 return textControl->indexForVisiblePosition(pos); | |
1846 } | |
1847 | |
1848 if (!isTextControl()) | |
1849 return 0; | |
1850 | |
1851 Node* node = m_renderer->node(); | |
1852 if (!node) | |
1853 return 0; | |
1854 | |
1855 Position indexPosition = pos.deepEquivalent(); | |
1856 if (indexPosition.isNull() || highestEditableRoot(indexPosition, HasEditable
AXRole) != node) | |
1857 return 0; | |
1858 | |
1859 RefPtrWillBeRawPtr<Range> range = Range::create(m_renderer->document()); | |
1860 range->setStart(node, 0, IGNORE_EXCEPTION); | |
1861 range->setEnd(indexPosition, IGNORE_EXCEPTION); | |
1862 | |
1863 return TextIterator::rangeLength(range.get()); | |
1864 } | |
1865 | |
1866 void AXRenderObject::addInlineTextBoxChildren() | |
1867 { | |
1868 Settings* settings = document()->settings(); | |
1869 if (!settings || !settings->inlineTextBoxAccessibilityEnabled()) | |
1870 return; | |
1871 | |
1872 if (!renderer() || !renderer()->isText()) | |
1873 return; | |
1874 | |
1875 if (renderer()->needsLayout()) { | |
1876 // If a RenderText needs layout, its inline text boxes are either | |
1877 // nonexistent or invalid, so defer until the layout happens and | |
1878 // the renderer calls AXObjectCacheImpl::inlineTextBoxesUpdated. | |
1879 return; | |
1880 } | |
1881 | |
1882 RenderText* renderText = toRenderText(renderer()); | |
1883 for (RefPtr<AbstractInlineTextBox> box = renderText->firstAbstractInlineText
Box(); box.get(); box = box->nextInlineTextBox()) { | |
1884 AXObject* axObject = axObjectCache()->getOrCreate(box.get()); | |
1885 if (!axObject->accessibilityIsIgnored()) | |
1886 m_children.append(axObject); | |
1887 } | |
1888 } | |
1889 | |
1890 void AXRenderObject::lineBreaks(Vector<int>& lineBreaks) const | |
1891 { | |
1892 if (!isTextControl()) | |
1893 return; | |
1894 | |
1895 VisiblePosition visiblePos = visiblePositionForIndex(0); | |
1896 VisiblePosition savedVisiblePos = visiblePos; | |
1897 visiblePos = nextLinePosition(visiblePos, 0); | |
1898 while (!visiblePos.isNull() && visiblePos != savedVisiblePos) { | |
1899 lineBreaks.append(indexForVisiblePosition(visiblePos)); | |
1900 savedVisiblePos = visiblePos; | |
1901 visiblePos = nextLinePosition(visiblePos, 0); | |
1902 } | |
1903 } | |
1904 | |
1905 // | |
1906 // Private. | |
1907 // | |
1908 | |
1909 bool AXRenderObject::isAllowedChildOfTree() const | |
1910 { | |
1911 // Determine if this is in a tree. If so, we apply special behavior to make
it work like an AXOutline. | |
1912 AXObject* axObj = parentObject(); | |
1913 bool isInTree = false; | |
1914 while (axObj) { | |
1915 if (axObj->isTree()) { | |
1916 isInTree = true; | |
1917 break; | |
1918 } | |
1919 axObj = axObj->parentObject(); | |
1920 } | |
1921 | |
1922 // If the object is in a tree, only tree items should be exposed (and the ch
ildren of tree items). | |
1923 if (isInTree) { | |
1924 AccessibilityRole role = roleValue(); | |
1925 if (role != TreeItemRole && role != StaticTextRole) | |
1926 return false; | |
1927 } | |
1928 return true; | |
1929 } | |
1930 | |
1931 void AXRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& re
sult) | |
1932 { | |
1933 bool isMulti = isMultiSelectable(); | |
1934 | |
1935 AccessibilityChildrenVector childObjects = children(); | |
1936 unsigned childrenSize = childObjects.size(); | |
1937 for (unsigned k = 0; k < childrenSize; ++k) { | |
1938 // Every child should have aria-role option, and if so, check for select
ed attribute/state. | |
1939 AXObject* child = childObjects[k].get(); | |
1940 if (child->isSelected() && child->ariaRoleAttribute() == ListBoxOptionRo
le) { | |
1941 result.append(child); | |
1942 if (!isMulti) | |
1943 return; | |
1944 } | |
1945 } | |
1946 } | |
1947 | |
1948 AXObject::PlainTextRange AXRenderObject::ariaSelectedTextRange() const | |
1949 { | |
1950 Node* node = m_renderer->node(); | |
1951 if (!node) | |
1952 return PlainTextRange(); | |
1953 | |
1954 VisibleSelection visibleSelection = selection(); | |
1955 RefPtrWillBeRawPtr<Range> currentSelectionRange = visibleSelection.toNormali
zedRange(); | |
1956 if (!currentSelectionRange || !currentSelectionRange->intersectsNode(node, I
GNORE_EXCEPTION)) | |
1957 return PlainTextRange(); | |
1958 | |
1959 int start = indexForVisiblePosition(visibleSelection.visibleStart()); | |
1960 int end = indexForVisiblePosition(visibleSelection.visibleEnd()); | |
1961 | |
1962 return PlainTextRange(start, end - start); | |
1963 } | |
1964 | |
1965 bool AXRenderObject::nodeIsTextControl(const Node* node) const | |
1966 { | |
1967 if (!node) | |
1968 return false; | |
1969 | |
1970 const AXObject* axObjectForNode = axObjectCache()->getOrCreate(const_cast<No
de*>(node)); | |
1971 if (!axObjectForNode) | |
1972 return false; | |
1973 | |
1974 return axObjectForNode->isTextControl(); | |
1975 } | |
1976 | |
1977 bool AXRenderObject::isTabItemSelected() const | |
1978 { | |
1979 if (!isTabItem() || !m_renderer) | |
1980 return false; | |
1981 | |
1982 Node* node = m_renderer->node(); | |
1983 if (!node || !node->isElementNode()) | |
1984 return false; | |
1985 | |
1986 // The ARIA spec says a tab item can also be selected if it is aria-labeled
by a tabpanel | |
1987 // that has keyboard focus inside of it, or if a tabpanel in its aria-contro
ls list has KB | |
1988 // focus inside of it. | |
1989 AXObject* focusedElement = focusedUIElement(); | |
1990 if (!focusedElement) | |
1991 return false; | |
1992 | |
1993 WillBeHeapVector<RawPtrWillBeMember<Element> > elements; | |
1994 elementsFromAttribute(elements, aria_controlsAttr); | |
1995 | |
1996 unsigned count = elements.size(); | |
1997 for (unsigned k = 0; k < count; ++k) { | |
1998 Element* element = elements[k]; | |
1999 AXObject* tabPanel = axObjectCache()->getOrCreate(element); | |
2000 | |
2001 // A tab item should only control tab panels. | |
2002 if (!tabPanel || tabPanel->roleValue() != TabPanelRole) | |
2003 continue; | |
2004 | |
2005 AXObject* checkFocusElement = focusedElement; | |
2006 // Check if the focused element is a descendant of the element controlle
d by the tab item. | |
2007 while (checkFocusElement) { | |
2008 if (tabPanel == checkFocusElement) | |
2009 return true; | |
2010 checkFocusElement = checkFocusElement->parentObject(); | |
2011 } | |
2012 } | |
2013 | |
2014 return false; | |
2015 } | |
2016 | |
2017 AXObject* AXRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, co
nst IntPoint& point) const | |
2018 { | |
2019 if (!area) | |
2020 return 0; | |
2021 | |
2022 AXObject* parent = axObjectCache()->getOrCreate(area->imageElement()); | |
2023 if (!parent) | |
2024 return 0; | |
2025 | |
2026 AXObject::AccessibilityChildrenVector children = parent->children(); | |
2027 unsigned count = children.size(); | |
2028 for (unsigned k = 0; k < count; ++k) { | |
2029 if (children[k]->elementRect().contains(point)) | |
2030 return children[k].get(); | |
2031 } | |
2032 | |
2033 return 0; | |
2034 } | |
2035 | |
2036 bool AXRenderObject::renderObjectIsObservable(RenderObject* renderer) const | |
2037 { | |
2038 // AX clients will listen for AXValueChange on a text control. | |
2039 if (renderer->isTextControl()) | |
2040 return true; | |
2041 | |
2042 // AX clients will listen for AXSelectedChildrenChanged on listboxes. | |
2043 Node* node = renderer->node(); | |
2044 if (nodeHasRole(node, "listbox") || (renderer->isBoxModelObject() && toRende
rBoxModelObject(renderer)->isListBox())) | |
2045 return true; | |
2046 | |
2047 // Textboxes should send out notifications. | |
2048 if (nodeHasRole(node, "textbox")) | |
2049 return true; | |
2050 | |
2051 return false; | |
2052 } | |
2053 | |
2054 RenderObject* AXRenderObject::renderParentObject() const | |
2055 { | |
2056 if (!m_renderer) | |
2057 return 0; | |
2058 | |
2059 RenderObject* startOfConts = m_renderer->isRenderBlock() ? startOfContinuati
ons(m_renderer) : 0; | |
2060 if (startOfConts) { | |
2061 // Case 1: node is a block and is an inline's continuation. Parent | |
2062 // is the start of the continuation chain. | |
2063 return startOfConts; | |
2064 } | |
2065 | |
2066 RenderObject* parent = m_renderer->parent(); | |
2067 startOfConts = parent && parent->isRenderInline() ? startOfContinuations(par
ent) : 0; | |
2068 if (startOfConts) { | |
2069 // Case 2: node's parent is an inline which is some node's continuation;
parent is | |
2070 // the earliest node in the continuation chain. | |
2071 return startOfConts; | |
2072 } | |
2073 | |
2074 RenderObject* firstChild = parent ? parent->slowFirstChild() : 0; | |
2075 if (firstChild && firstChild->node()) { | |
2076 // Case 3: The first sibling is the beginning of a continuation chain. F
ind the origin of that continuation. | |
2077 // Get the node's renderer and follow that continuation chain until the
first child is found. | |
2078 for (RenderObject* nodeRenderFirstChild = firstChild->node()->renderer()
; nodeRenderFirstChild != firstChild; nodeRenderFirstChild = firstChild->node()-
>renderer()) { | |
2079 for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; cont
sTest = nextContinuation(contsTest)) { | |
2080 if (contsTest == firstChild) { | |
2081 parent = nodeRenderFirstChild->parent(); | |
2082 break; | |
2083 } | |
2084 } | |
2085 RenderObject* newFirstChild = parent->slowFirstChild(); | |
2086 if (firstChild == newFirstChild) | |
2087 break; | |
2088 firstChild = newFirstChild; | |
2089 if (!firstChild->node()) | |
2090 break; | |
2091 } | |
2092 } | |
2093 | |
2094 return parent; | |
2095 } | |
2096 | |
2097 bool AXRenderObject::isDescendantOfElementType(const HTMLQualifiedName& tagName)
const | |
2098 { | |
2099 for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->p
arent()) { | |
2100 if (parent->node() && parent->node()->hasTagName(tagName)) | |
2101 return true; | |
2102 } | |
2103 return false; | |
2104 } | |
2105 | |
2106 bool AXRenderObject::isSVGImage() const | |
2107 { | |
2108 return remoteSVGRootElement(); | |
2109 } | |
2110 | |
2111 void AXRenderObject::detachRemoteSVGRoot() | |
2112 { | |
2113 if (AXSVGRoot* root = remoteSVGRootElement()) | |
2114 root->setParent(0); | |
2115 } | |
2116 | |
2117 AXSVGRoot* AXRenderObject::remoteSVGRootElement() const | |
2118 { | |
2119 if (!m_renderer || !m_renderer->isRenderImage()) | |
2120 return 0; | |
2121 | |
2122 ImageResource* cachedImage = toRenderImage(m_renderer)->cachedImage(); | |
2123 if (!cachedImage) | |
2124 return 0; | |
2125 | |
2126 Image* image = cachedImage->image(); | |
2127 if (!image || !image->isSVGImage()) | |
2128 return 0; | |
2129 | |
2130 FrameView* frameView = toSVGImage(image)->frameView(); | |
2131 if (!frameView) | |
2132 return 0; | |
2133 Document* doc = frameView->frame().document(); | |
2134 if (!doc || !doc->isSVGDocument()) | |
2135 return 0; | |
2136 | |
2137 Settings* settings = doc->settings(); | |
2138 if (settings && !settings->accessibilityEnabled()) | |
2139 settings->setAccessibilityEnabled(true); | |
2140 | |
2141 SVGSVGElement* rootElement = doc->accessSVGExtensions().rootElement(); | |
2142 if (!rootElement) | |
2143 return 0; | |
2144 RenderObject* rendererRoot = rootElement->renderer(); | |
2145 if (!rendererRoot) | |
2146 return 0; | |
2147 | |
2148 AXObject* rootSVGObject = toAXObjectCacheImpl(doc->axObjectCache())->getOrCr
eate(rendererRoot); | |
2149 | |
2150 // In order to connect the AX hierarchy from the SVG root element from the l
oaded resource | |
2151 // the parent must be set, because there's no other way to get back to who c
reated the image. | |
2152 ASSERT(rootSVGObject && rootSVGObject->isAXSVGRoot()); | |
2153 if (!rootSVGObject->isAXSVGRoot()) | |
2154 return 0; | |
2155 | |
2156 return toAXSVGRoot(rootSVGObject); | |
2157 } | |
2158 | |
2159 AXObject* AXRenderObject::remoteSVGElementHitTest(const IntPoint& point) const | |
2160 { | |
2161 AXObject* remote = remoteSVGRootElement(); | |
2162 if (!remote) | |
2163 return 0; | |
2164 | |
2165 IntSize offset = point - roundedIntPoint(elementRect().location()); | |
2166 return remote->accessibilityHitTest(IntPoint(offset)); | |
2167 } | |
2168 | |
2169 // The boundingBox for elements within the remote SVG element needs to be offset
by its position | |
2170 // within the parent page, otherwise they are in relative coordinates only. | |
2171 void AXRenderObject::offsetBoundingBoxForRemoteSVGElement(LayoutRect& rect) cons
t | |
2172 { | |
2173 for (AXObject* parent = parentObject(); parent; parent = parent->parentObjec
t()) { | |
2174 if (parent->isAXSVGRoot()) { | |
2175 rect.moveBy(parent->parentObject()->elementRect().location()); | |
2176 break; | |
2177 } | |
2178 } | |
2179 } | |
2180 | |
2181 // Hidden children are those that are not rendered or visible, but are specifica
lly marked as aria-hidden=false, | |
2182 // meaning that they should be exposed to the AX hierarchy. | |
2183 void AXRenderObject::addHiddenChildren() | |
2184 { | |
2185 Node* node = this->node(); | |
2186 if (!node) | |
2187 return; | |
2188 | |
2189 // First do a quick run through to determine if we have any hidden nodes (mo
st often we will not). | |
2190 // If we do have hidden nodes, we need to determine where to insert them so
they match DOM order as close as possible. | |
2191 bool shouldInsertHiddenNodes = false; | |
2192 for (Node* child = node->firstChild(); child; child = child->nextSibling())
{ | |
2193 if (!child->renderer() && isNodeAriaVisible(child)) { | |
2194 shouldInsertHiddenNodes = true; | |
2195 break; | |
2196 } | |
2197 } | |
2198 | |
2199 if (!shouldInsertHiddenNodes) | |
2200 return; | |
2201 | |
2202 // Iterate through all of the children, including those that may have alread
y been added, and | |
2203 // try to insert hidden nodes in the correct place in the DOM order. | |
2204 unsigned insertionIndex = 0; | |
2205 for (Node* child = node->firstChild(); child; child = child->nextSibling())
{ | |
2206 if (child->renderer()) { | |
2207 // Find out where the last render sibling is located within m_childr
en. | |
2208 AXObject* childObject = axObjectCache()->get(child->renderer()); | |
2209 if (childObject && childObject->accessibilityIsIgnored()) { | |
2210 AccessibilityChildrenVector children = childObject->children(); | |
2211 if (children.size()) | |
2212 childObject = children.last().get(); | |
2213 else | |
2214 childObject = 0; | |
2215 } | |
2216 | |
2217 if (childObject) | |
2218 insertionIndex = m_children.find(childObject) + 1; | |
2219 continue; | |
2220 } | |
2221 | |
2222 if (!isNodeAriaVisible(child)) | |
2223 continue; | |
2224 | |
2225 unsigned previousSize = m_children.size(); | |
2226 if (insertionIndex > previousSize) | |
2227 insertionIndex = previousSize; | |
2228 | |
2229 insertChild(axObjectCache()->getOrCreate(child), insertionIndex); | |
2230 insertionIndex += (m_children.size() - previousSize); | |
2231 } | |
2232 } | |
2233 | |
2234 void AXRenderObject::addTextFieldChildren() | |
2235 { | |
2236 Node* node = this->node(); | |
2237 if (!isHTMLInputElement(node)) | |
2238 return; | |
2239 | |
2240 HTMLInputElement& input = toHTMLInputElement(*node); | |
2241 Element* spinButtonElement = input.userAgentShadowRoot()->getElementById(Sha
dowElementNames::spinButton()); | |
2242 if (!spinButtonElement || !spinButtonElement->isSpinButtonElement()) | |
2243 return; | |
2244 | |
2245 AXSpinButton* axSpinButton = toAXSpinButton(axObjectCache()->getOrCreate(Spi
nButtonRole)); | |
2246 axSpinButton->setSpinButtonElement(toSpinButtonElement(spinButtonElement)); | |
2247 axSpinButton->setParent(this); | |
2248 m_children.append(axSpinButton); | |
2249 } | |
2250 | |
2251 void AXRenderObject::addImageMapChildren() | |
2252 { | |
2253 RenderBoxModelObject* cssBox = renderBoxModelObject(); | |
2254 if (!cssBox || !cssBox->isRenderImage()) | |
2255 return; | |
2256 | |
2257 HTMLMapElement* map = toRenderImage(cssBox)->imageMap(); | |
2258 if (!map) | |
2259 return; | |
2260 | |
2261 for (HTMLAreaElement& area : Traversal<HTMLAreaElement>::descendantsOf(*map)
) { | |
2262 // add an <area> element for this child if it has a link | |
2263 if (area.isLink()) { | |
2264 AXImageMapLink* areaObject = toAXImageMapLink(axObjectCache()->getOr
Create(ImageMapLinkRole)); | |
2265 areaObject->setHTMLAreaElement(&area); | |
2266 areaObject->setHTMLMapElement(map); | |
2267 areaObject->setParent(this); | |
2268 if (!areaObject->accessibilityIsIgnored()) | |
2269 m_children.append(areaObject); | |
2270 else | |
2271 axObjectCache()->remove(areaObject->axObjectID()); | |
2272 } | |
2273 } | |
2274 } | |
2275 | |
2276 void AXRenderObject::addCanvasChildren() | |
2277 { | |
2278 if (!isHTMLCanvasElement(node())) | |
2279 return; | |
2280 | |
2281 // If it's a canvas, it won't have rendered children, but it might have acce
ssible fallback content. | |
2282 // Clear m_haveChildren because AXNodeObject::addChildren will expect it to
be false. | |
2283 ASSERT(!m_children.size()); | |
2284 m_haveChildren = false; | |
2285 AXNodeObject::addChildren(); | |
2286 } | |
2287 | |
2288 void AXRenderObject::addAttachmentChildren() | |
2289 { | |
2290 if (!isAttachment()) | |
2291 return; | |
2292 | |
2293 // FrameView's need to be inserted into the AX hierarchy when encountered. | |
2294 Widget* widget = widgetForAttachmentView(); | |
2295 if (!widget || !widget->isFrameView()) | |
2296 return; | |
2297 | |
2298 AXObject* axWidget = axObjectCache()->getOrCreate(widget); | |
2299 if (!axWidget->accessibilityIsIgnored()) | |
2300 m_children.append(axWidget); | |
2301 } | |
2302 | |
2303 void AXRenderObject::addPopupChildren() | |
2304 { | |
2305 if (!isHTMLInputElement(node())) | |
2306 return; | |
2307 if (AXObject* axPopup = toHTMLInputElement(node())->popupRootAXObject()) | |
2308 m_children.append(axPopup); | |
2309 } | |
2310 | |
2311 void AXRenderObject::addRemoteSVGChildren() | |
2312 { | |
2313 AXSVGRoot* root = remoteSVGRootElement(); | |
2314 if (!root) | |
2315 return; | |
2316 | |
2317 root->setParent(this); | |
2318 | |
2319 if (root->accessibilityIsIgnored()) { | |
2320 AccessibilityChildrenVector children = root->children(); | |
2321 unsigned length = children.size(); | |
2322 for (unsigned i = 0; i < length; ++i) | |
2323 m_children.append(children[i]); | |
2324 } else { | |
2325 m_children.append(root); | |
2326 } | |
2327 } | |
2328 | |
2329 void AXRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result) | |
2330 { | |
2331 // Get all the rows. | |
2332 AccessibilityChildrenVector allRows; | |
2333 if (isTree()) | |
2334 ariaTreeRows(allRows); | |
2335 else if (isAXTable() && toAXTable(this)->supportsSelectedRows()) | |
2336 allRows = toAXTable(this)->rows(); | |
2337 | |
2338 // Determine which rows are selected. | |
2339 bool isMulti = isMultiSelectable(); | |
2340 | |
2341 // Prefer active descendant over aria-selected. | |
2342 AXObject* activeDesc = activeDescendant(); | |
2343 if (activeDesc && (activeDesc->isTreeItem() || activeDesc->isTableRow())) { | |
2344 result.append(activeDesc); | |
2345 if (!isMulti) | |
2346 return; | |
2347 } | |
2348 | |
2349 unsigned count = allRows.size(); | |
2350 for (unsigned k = 0; k < count; ++k) { | |
2351 if (allRows[k]->isSelected()) { | |
2352 result.append(allRows[k]); | |
2353 if (!isMulti) | |
2354 break; | |
2355 } | |
2356 } | |
2357 } | |
2358 | |
2359 bool AXRenderObject::elementAttributeValue(const QualifiedName& attributeName) c
onst | |
2360 { | |
2361 if (!m_renderer) | |
2362 return false; | |
2363 | |
2364 return equalIgnoringCase(getAttribute(attributeName), "true"); | |
2365 } | |
2366 | |
2367 bool AXRenderObject::inheritsPresentationalRole() const | |
2368 { | |
2369 // ARIA states if an item can get focus, it should not be presentational. | |
2370 if (canSetFocusAttribute()) | |
2371 return false; | |
2372 | |
2373 // ARIA spec says that when a parent object is presentational, and it has re
quired child elements, | |
2374 // those child elements are also presentational. For example, <li> becomes p
resentational from <ul>. | |
2375 // http://www.w3.org/WAI/PF/aria/complete#presentation | |
2376 if (roleValue() != ListItemRole && roleValue() != ListMarkerRole) | |
2377 return false; | |
2378 | |
2379 AXObject* parent = parentObject(); | |
2380 if (!parent->isAXRenderObject()) | |
2381 return false; | |
2382 | |
2383 Node* elementNode = toAXRenderObject(parent)->node(); | |
2384 if (!elementNode || !elementNode->isElementNode()) | |
2385 return false; | |
2386 | |
2387 QualifiedName tagName = toElement(elementNode)->tagQName(); | |
2388 if (tagName == ulTag || tagName == olTag || tagName == dlTag) | |
2389 return (parent->roleValue() == NoneRole || parent->roleValue() == Presen
tationalRole); | |
2390 | |
2391 return false; | |
2392 } | |
2393 | |
2394 LayoutRect AXRenderObject::computeElementRect() const | |
2395 { | |
2396 RenderObject* obj = m_renderer; | |
2397 | |
2398 if (!obj) | |
2399 return LayoutRect(); | |
2400 | |
2401 if (obj->node()) // If we are a continuation, we want to make sure to use th
e primary renderer. | |
2402 obj = obj->node()->renderer(); | |
2403 | |
2404 // absoluteFocusRingBoundingBox will query the hierarchy below this element,
which for large webpages can be very slow. | |
2405 // For a web area, which will have the most elements of any element, absolut
eQuads should be used. | |
2406 // We should also use absoluteQuads for SVG elements, otherwise transforms w
on't be applied. | |
2407 | |
2408 LayoutRect result; | |
2409 if (obj->isText()) { | |
2410 Vector<FloatQuad> quads; | |
2411 toRenderText(obj)->absoluteQuads(quads, 0, RenderText::ClipToEllipsis); | |
2412 result = boundingBoxForQuads(obj, quads); | |
2413 } else if (isWebArea() || obj->isSVGRoot()) { | |
2414 result = obj->absoluteBoundingBoxRect(); | |
2415 } else { | |
2416 result = obj->absoluteFocusRingBoundingBoxRect(); | |
2417 } | |
2418 | |
2419 Document* document = this->document(); | |
2420 if (document && document->isSVGDocument()) | |
2421 offsetBoundingBoxForRemoteSVGElement(result); | |
2422 if (document && document->frame() && document->frame()->pagePopupOwner()) { | |
2423 IntPoint popupOrigin = document->view()->contentsToScreen(IntRect()).loc
ation(); | |
2424 IntPoint mainOrigin = axObjectCache()->rootObject()->documentFrameView()
->contentsToScreen(IntRect()).location(); | |
2425 result.moveBy(IntPoint(popupOrigin - mainOrigin)); | |
2426 } | |
2427 | |
2428 // The size of the web area should be the content size, not the clipped size
. | |
2429 if (isWebArea() && obj->frame()->view()) | |
2430 result.setSize(obj->frame()->view()->contentsSize()); | |
2431 | |
2432 // Checkboxes and radio buttons include their label as part of their rect. | |
2433 if (isCheckboxOrRadio()) { | |
2434 HTMLLabelElement* label = labelForElement(toElement(m_renderer->node()))
; | |
2435 if (label && label->renderer()) { | |
2436 LayoutRect labelRect = axObjectCache()->getOrCreate(label)->elementR
ect(); | |
2437 result.unite(labelRect); | |
2438 } | |
2439 } | |
2440 | |
2441 return result; | |
2442 } | |
2443 | |
2444 } // namespace blink | |
OLD | NEW |