diff options
author | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
---|---|---|
committer | Lorry Tar Creator <lorry-tar-importer@lorry> | 2017-06-27 06:07:23 +0000 |
commit | 1bf1084f2b10c3b47fd1a588d85d21ed0eb41d0c (patch) | |
tree | 46dcd36c86e7fbc6e5df36deb463b33e9967a6f7 /Source/WebCore/accessibility/AccessibilityRenderObject.cpp | |
parent | 32761a6cee1d0dee366b885b7b9c777e67885688 (diff) | |
download | WebKitGtk-tarball-master.tar.gz |
webkitgtk-2.16.5HEADwebkitgtk-2.16.5master
Diffstat (limited to 'Source/WebCore/accessibility/AccessibilityRenderObject.cpp')
-rw-r--r-- | Source/WebCore/accessibility/AccessibilityRenderObject.cpp | 2195 |
1 files changed, 1069 insertions, 1126 deletions
diff --git a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp index 80e59420e..3b7edcaa9 100644 --- a/Source/WebCore/accessibility/AccessibilityRenderObject.cpp +++ b/Source/WebCore/accessibility/AccessibilityRenderObject.cpp @@ -10,7 +10,7 @@ * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. -* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of +* 3. Neither the name of Apple Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * @@ -36,32 +36,33 @@ #include "AccessibilitySpinButton.h" #include "AccessibilityTable.h" #include "CachedImage.h" -#include "Chrome.h" #include "ElementIterator.h" -#include "EventNames.h" #include "FloatRect.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameSelection.h" #include "HTMLAreaElement.h" +#include "HTMLAudioElement.h" +#include "HTMLDetailsElement.h" #include "HTMLFormElement.h" #include "HTMLFrameElementBase.h" #include "HTMLImageElement.h" #include "HTMLInputElement.h" #include "HTMLLabelElement.h" #include "HTMLMapElement.h" +#include "HTMLMeterElement.h" #include "HTMLNames.h" -#include "HTMLOptGroupElement.h" #include "HTMLOptionElement.h" #include "HTMLOptionsCollection.h" #include "HTMLSelectElement.h" +#include "HTMLSummaryElement.h" #include "HTMLTableElement.h" #include "HTMLTextAreaElement.h" +#include "HTMLVideoElement.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "Image.h" #include "LocalizedStrings.h" -#include "MathMLNames.h" #include "NodeList.h" #include "Page.h" #include "ProgressTracker.h" @@ -75,13 +76,13 @@ #include "RenderLayer.h" #include "RenderLineBreak.h" #include "RenderListBox.h" +#include "RenderListItem.h" #include "RenderListMarker.h" #include "RenderMathMLBlock.h" -#include "RenderMathMLFraction.h" -#include "RenderMathMLOperator.h" #include "RenderMenuList.h" #include "RenderSVGRoot.h" #include "RenderSVGShape.h" +#include "RenderTableCell.h" #include "RenderText.h" #include "RenderTextControl.h" #include "RenderTextControlSingleLine.h" @@ -92,15 +93,14 @@ #include "RenderedPosition.h" #include "SVGDocument.h" #include "SVGImage.h" -#include "SVGImageChromeClient.h" -#include "SVGNames.h" #include "SVGSVGElement.h" #include "Text.h" #include "TextControlInnerElements.h" +#include "TextIterator.h" #include "VisibleUnits.h" #include "htmlediting.h" +#include <wtf/NeverDestroyed.h> #include <wtf/StdLibExtras.h> -#include <wtf/text/StringBuilder.h> #include <wtf/unicode/CharacterNames.h> namespace WebCore { @@ -126,9 +126,9 @@ void AccessibilityRenderObject::init() AccessibilityNodeObject::init(); } -PassRefPtr<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) +Ref<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) { - return adoptRef(new AccessibilityRenderObject(renderer)); + return adoptRef(*new AccessibilityRenderObject(renderer)); } void AccessibilityRenderObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) @@ -141,14 +141,14 @@ void AccessibilityRenderObject::detach(AccessibilityDetachmentType detachmentTyp if (m_renderer) m_renderer->setHasAXObject(false); #endif - m_renderer = 0; + m_renderer = nullptr; } RenderBoxModelObject* AccessibilityRenderObject::renderBoxModelObject() const { - if (!m_renderer || !m_renderer->isBoxModelObject()) - return 0; - return toRenderBoxModelObject(m_renderer); + if (!is<RenderBoxModelObject>(m_renderer)) + return nullptr; + return downcast<RenderBoxModelObject>(m_renderer); } void AccessibilityRenderObject::setRenderer(RenderObject* renderer) @@ -157,16 +157,9 @@ void AccessibilityRenderObject::setRenderer(RenderObject* renderer) setNode(renderer->node()); } -static inline bool isInlineWithContinuation(RenderObject* object) +static inline bool isInlineWithContinuation(RenderObject& object) { - if (!object->isBoxModelObject()) - return false; - - RenderBoxModelObject* renderer = toRenderBoxModelObject(object); - if (!renderer->isRenderInline()) - return false; - - return toRenderInline(renderer)->continuation(); + return is<RenderInline>(object) && downcast<RenderInline>(object).continuation(); } static inline RenderObject* firstChildInContinuation(RenderInline& renderer) @@ -174,47 +167,51 @@ static inline RenderObject* firstChildInContinuation(RenderInline& renderer) auto continuation = renderer.continuation(); while (continuation) { - if (continuation->isRenderBlock()) + if (is<RenderBlock>(*continuation)) return continuation; if (RenderObject* child = continuation->firstChild()) return child; - continuation = toRenderInline(continuation)->continuation(); + continuation = downcast<RenderInline>(*continuation).continuation(); } return nullptr; } -static inline RenderObject* firstChildConsideringContinuation(RenderObject* renderer) +static inline RenderObject* firstChildConsideringContinuation(RenderObject& renderer) { - RenderObject* firstChild = renderer->firstChildSlow(); + RenderObject* firstChild = renderer.firstChildSlow(); + // We don't want to include the end of a continuation as the firstChild of the + // anonymous parent, because everything has already been linked up via continuation. + // CSS first-letter selector is an example of this case. + if (renderer.isAnonymous() && firstChild && firstChild->isInlineElementContinuation()) + firstChild = nullptr; + if (!firstChild && isInlineWithContinuation(renderer)) - firstChild = firstChildInContinuation(toRenderInline(*renderer)); + firstChild = firstChildInContinuation(downcast<RenderInline>(renderer)); return firstChild; } -static inline RenderObject* lastChildConsideringContinuation(RenderObject* renderer) +static inline RenderObject* lastChildConsideringContinuation(RenderObject& renderer) { - RenderObject* lastChild = renderer->lastChildSlow(); - RenderObject* prev; - RenderObject* cur = renderer; - - if (!cur->isRenderInline() && !cur->isRenderBlock()) - return renderer; + if (!is<RenderInline>(renderer) && !is<RenderBlock>(renderer)) + return &renderer; - while (cur) { - prev = cur; + RenderObject* lastChild = downcast<RenderBoxModelObject>(renderer).lastChild(); + RenderBoxModelObject* previous; + for (auto* current = &downcast<RenderBoxModelObject>(renderer); current; ) { + previous = current; - if (RenderObject* lc = cur->lastChildSlow()) - lastChild = lc; + if (RenderObject* newLastChild = current->lastChild()) + lastChild = newLastChild; - if (cur->isRenderInline()) { - cur = toRenderInline(cur)->inlineElementContinuation(); - ASSERT_UNUSED(prev, cur || !toRenderInline(prev)->continuation()); + if (is<RenderInline>(*current)) { + current = downcast<RenderInline>(*current).inlineElementContinuation(); + ASSERT_UNUSED(previous, current || !downcast<RenderInline>(*previous).continuation()); } else - cur = toRenderBlock(cur)->inlineElementContinuation(); + current = downcast<RenderBlock>(*current).inlineElementContinuation(); } return lastChild; @@ -223,9 +220,9 @@ static inline RenderObject* lastChildConsideringContinuation(RenderObject* rende AccessibilityObject* AccessibilityRenderObject::firstChild() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* firstChild = firstChildConsideringContinuation(m_renderer); + RenderObject* firstChild = firstChildConsideringContinuation(*m_renderer); // If an object can't have children, then it is using this method to help // calculate some internal property (like its description). @@ -240,9 +237,9 @@ AccessibilityObject* AccessibilityRenderObject::firstChild() const AccessibilityObject* AccessibilityRenderObject::lastChild() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* lastChild = lastChildConsideringContinuation(m_renderer); + RenderObject* lastChild = lastChildConsideringContinuation(*m_renderer); if (!lastChild && !canHaveChildren()) return AccessibilityNodeObject::lastChild(); @@ -250,104 +247,91 @@ AccessibilityObject* AccessibilityRenderObject::lastChild() const return axObjectCache()->getOrCreate(lastChild); } -static inline RenderInline* startOfContinuations(RenderObject* r) +static inline RenderInline* startOfContinuations(RenderObject& renderer) { - if (r->isInlineElementContinuation()) { -#if ENABLE(MATHML) - // MathML elements make anonymous RenderObjects, then set their node to the parent's node. - // This makes it so that the renderer() != renderer()->node()->renderer() - // (which is what isInlineElementContinuation() uses as a determinant). - if (r->node()->isMathMLElement()) - return nullptr; -#endif - - return toRenderInline(r->node()->renderer()); - } + if (renderer.isInlineElementContinuation() && is<RenderInline>(renderer.node()->renderer())) + return downcast<RenderInline>(renderer.node()->renderer()); // Blocks with a previous continuation always have a next continuation - if (r->isRenderBlock() && toRenderBlock(r)->inlineElementContinuation()) - return toRenderInline(toRenderBlock(r)->inlineElementContinuation()->element()->renderer()); + if (is<RenderBlock>(renderer) && downcast<RenderBlock>(renderer).inlineElementContinuation()) + return downcast<RenderInline>(downcast<RenderBlock>(renderer).inlineElementContinuation()->element()->renderer()); - return 0; + return nullptr; } -static inline RenderObject* endOfContinuations(RenderObject* renderer) +static inline RenderObject* endOfContinuations(RenderObject& renderer) { - RenderObject* prev = renderer; - RenderObject* cur = renderer; - - if (!cur->isRenderInline() && !cur->isRenderBlock()) - return renderer; + if (!is<RenderInline>(renderer) && !is<RenderBlock>(renderer)) + return &renderer; - while (cur) { - prev = cur; - if (cur->isRenderInline()) { - cur = toRenderInline(cur)->inlineElementContinuation(); - ASSERT(cur || !toRenderInline(prev)->continuation()); + auto* previous = &downcast<RenderBoxModelObject>(renderer); + for (auto* current = previous; current; ) { + previous = current; + if (is<RenderInline>(*current)) { + current = downcast<RenderInline>(*current).inlineElementContinuation(); + ASSERT(current || !downcast<RenderInline>(*previous).continuation()); } else - cur = toRenderBlock(cur)->inlineElementContinuation(); + current = downcast<RenderBlock>(*current).inlineElementContinuation(); } - return prev; + return previous; } -static inline RenderObject* childBeforeConsideringContinuations(RenderInline* r, RenderObject* child) +static inline RenderObject* childBeforeConsideringContinuations(RenderInline* renderer, RenderObject* child) { - RenderBoxModelObject* curContainer = r; - RenderObject* cur = 0; - RenderObject* prev = 0; - - while (curContainer) { - if (curContainer->isRenderInline()) { - cur = curContainer->firstChild(); - while (cur) { - if (cur == child) - return prev; - prev = cur; - cur = cur->nextSibling(); + RenderObject* previous = nullptr; + for (RenderBoxModelObject* currentContainer = renderer; currentContainer; ) { + if (is<RenderInline>(*currentContainer)) { + auto* current = currentContainer->firstChild(); + while (current) { + if (current == child) + return previous; + previous = current; + current = current->nextSibling(); } - curContainer = toRenderInline(curContainer)->continuation(); - } else if (curContainer->isRenderBlock()) { - if (curContainer == child) - return prev; + currentContainer = downcast<RenderInline>(*currentContainer).continuation(); + } else if (is<RenderBlock>(*currentContainer)) { + if (currentContainer == child) + return previous; - prev = curContainer; - curContainer = toRenderBlock(curContainer)->inlineElementContinuation(); + previous = currentContainer; + currentContainer = downcast<RenderBlock>(*currentContainer).inlineElementContinuation(); } } ASSERT_NOT_REACHED(); - - return 0; + return nullptr; } -static inline bool firstChildIsInlineContinuation(RenderObject* renderer) +static inline bool firstChildIsInlineContinuation(RenderElement& renderer) { - RenderObject* child = renderer->firstChildSlow(); + RenderObject* child = renderer.firstChild(); return child && child->isInlineElementContinuation(); } AccessibilityObject* AccessibilityRenderObject::previousSibling() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* previousSibling = 0; + RenderObject* previousSibling = nullptr; // Case 1: The node is a block and is an inline's continuation. In that case, the inline's // last child is our previous sibling (or further back in the continuation chain) RenderInline* startOfConts; - if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) + if (is<RenderBox>(*m_renderer) && (startOfConts = startOfContinuations(*m_renderer))) previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer); // Case 2: Anonymous block parent of the end of a continuation - skip all the way to before // the parent of the start, since everything in between will be linked up via the continuation. - else if (m_renderer->isAnonymousBlock() && firstChildIsInlineContinuation(m_renderer)) { - RenderObject* firstParent = startOfContinuations(m_renderer->firstChildSlow())->parent(); - while (firstChildIsInlineContinuation(firstParent)) - firstParent = startOfContinuations(firstParent->firstChildSlow())->parent(); + else if (m_renderer->isAnonymousBlock() && firstChildIsInlineContinuation(downcast<RenderBlock>(*m_renderer))) { + RenderBlock& renderBlock = downcast<RenderBlock>(*m_renderer); + auto* firstParent = startOfContinuations(*renderBlock.firstChild())->parent(); + ASSERT(firstParent); + while (firstChildIsInlineContinuation(*firstParent)) + firstParent = startOfContinuations(*firstParent->firstChild())->parent(); previousSibling = firstParent->previousSibling(); } @@ -357,40 +341,41 @@ AccessibilityObject* AccessibilityRenderObject::previousSibling() const // Case 4: This node has no previous siblings, but its parent is an inline, // and is another node's inline continutation. Follow the continuation chain. - else if (m_renderer->parent()->isRenderInline() && (startOfConts = startOfContinuations(m_renderer->parent()))) + else if (is<RenderInline>(*m_renderer->parent()) && (startOfConts = startOfContinuations(*m_renderer->parent()))) previousSibling = childBeforeConsideringContinuations(startOfConts, m_renderer->parent()->firstChild()); if (!previousSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(previousSibling); } -static inline bool lastChildHasContinuation(RenderObject* renderer) +static inline bool lastChildHasContinuation(RenderElement& renderer) { - RenderObject* child = renderer->lastChildSlow(); - return child && isInlineWithContinuation(child); + RenderObject* child = renderer.lastChild(); + return child && isInlineWithContinuation(*child); } AccessibilityObject* AccessibilityRenderObject::nextSibling() const { if (!m_renderer) - return 0; + return nullptr; - RenderObject* nextSibling = 0; + RenderObject* nextSibling = nullptr; // Case 1: node is a block and has an inline continuation. Next sibling is the inline continuation's // first child. RenderInline* inlineContinuation; - if (m_renderer->isRenderBlock() && (inlineContinuation = toRenderBlock(m_renderer)->inlineElementContinuation())) - nextSibling = firstChildConsideringContinuation(inlineContinuation); + if (is<RenderBlock>(*m_renderer) && (inlineContinuation = downcast<RenderBlock>(*m_renderer).inlineElementContinuation())) + nextSibling = firstChildConsideringContinuation(*inlineContinuation); // Case 2: Anonymous block parent of the start of a continuation - skip all the way to // after the parent of the end, since everything in between will be linked up via the continuation. - else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(m_renderer)) { - RenderElement* lastParent = endOfContinuations(toRenderBlock(m_renderer)->lastChild())->parent(); - while (lastChildHasContinuation(lastParent)) - lastParent = endOfContinuations(lastParent->lastChild())->parent(); + else if (m_renderer->isAnonymousBlock() && lastChildHasContinuation(downcast<RenderBlock>(*m_renderer))) { + RenderElement* lastParent = endOfContinuations(*downcast<RenderBlock>(*m_renderer).lastChild())->parent(); + ASSERT(lastParent); + while (lastChildHasContinuation(*lastParent)) + lastParent = endOfContinuations(*lastParent->lastChild())->parent(); nextSibling = lastParent->nextSibling(); } @@ -400,54 +385,61 @@ AccessibilityObject* AccessibilityRenderObject::nextSibling() const // Case 4: node is an inline with a continuation. Next sibling is the next sibling of the end // of the continuation chain. - else if (isInlineWithContinuation(m_renderer)) - nextSibling = endOfContinuations(m_renderer)->nextSibling(); + else if (isInlineWithContinuation(*m_renderer)) + nextSibling = endOfContinuations(*m_renderer)->nextSibling(); // Case 5: node has no next sibling, and its parent is an inline with a continuation. - else if (isInlineWithContinuation(m_renderer->parent())) { - auto continuation = toRenderInline(m_renderer->parent())->continuation(); + // Case 5.1: After case 4, (the element was inline w/ continuation but had no sibling), then check it's parent. + if (!nextSibling && isInlineWithContinuation(*m_renderer->parent())) { + auto& continuation = *downcast<RenderInline>(*m_renderer->parent()).continuation(); // Case 5a: continuation is a block - in this case the block itself is the next sibling. - if (continuation->isRenderBlock()) - nextSibling = continuation; + if (is<RenderBlock>(continuation)) + nextSibling = &continuation; // Case 5b: continuation is an inline - in this case the inline's first child is the next sibling else nextSibling = firstChildConsideringContinuation(continuation); + + // After case 4, there are chances that nextSibling has the same node as the current renderer, + // which might lead to adding the same child repeatedly. + if (nextSibling && nextSibling->node() == m_renderer->node()) { + if (AccessibilityObject* nextObj = axObjectCache()->getOrCreate(nextSibling)) + return nextObj->nextSibling(); + } } if (!nextSibling) - return 0; + return nullptr; return axObjectCache()->getOrCreate(nextSibling); } -static RenderBoxModelObject* nextContinuation(RenderObject* renderer) +static RenderBoxModelObject* nextContinuation(RenderObject& renderer) { - ASSERT(renderer); - if (renderer->isRenderInline() && !renderer->isReplaced()) - return toRenderInline(renderer)->continuation(); - if (renderer->isRenderBlock()) - return toRenderBlock(renderer)->inlineElementContinuation(); - return 0; + if (is<RenderInline>(renderer) && !renderer.isReplaced()) + return downcast<RenderInline>(renderer).continuation(); + if (is<RenderBlock>(renderer)) + return downcast<RenderBlock>(renderer).inlineElementContinuation(); + return nullptr; } RenderObject* AccessibilityRenderObject::renderParentObject() const { if (!m_renderer) - return 0; + return nullptr; RenderElement* parent = m_renderer->parent(); // Case 1: node is a block and is an inline's continuation. Parent // is the start of the continuation chain. RenderInline* startOfConts = nullptr; - RenderObject* firstChild = 0; - if (m_renderer->isRenderBlock() && (startOfConts = startOfContinuations(m_renderer))) + RenderObject* firstChild = nullptr; + if (is<RenderBlock>(*m_renderer) && (startOfConts = startOfContinuations(*m_renderer))) parent = startOfConts; // Case 2: node's parent is an inline which is some node's continuation; parent is // the earliest node in the continuation chain. - else if (parent && parent->isRenderInline() && (startOfConts = startOfContinuations(parent))) + else if (is<RenderInline>(parent) && (startOfConts = startOfContinuations(*parent))) parent = startOfConts; // Case 3: The first sibling is the beginning of a continuation chain. Find the origin of that continuation. @@ -455,7 +447,7 @@ RenderObject* AccessibilityRenderObject::renderParentObject() const // Get the node's renderer and follow that continuation chain until the first child is found RenderObject* nodeRenderFirstChild = firstChild->node()->renderer(); while (nodeRenderFirstChild != firstChild) { - for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(contsTest)) { + for (RenderObject* contsTest = nodeRenderFirstChild; contsTest; contsTest = nextContinuation(*contsTest)) { if (contsTest == firstChild) { parent = nodeRenderFirstChild->parent(); break; @@ -476,17 +468,21 @@ RenderObject* AccessibilityRenderObject::renderParentObject() const AccessibilityObject* AccessibilityRenderObject::parentObjectIfExists() const { + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + // WebArea's parent should be the scroll view containing it. - if (isWebArea() || isSeamlessWebArea()) - return axObjectCache()->get(&m_renderer->view().frameView()); + if (isWebArea()) + return cache->get(&m_renderer->view().frameView()); - return axObjectCache()->get(renderParentObject()); + return cache->get(renderParentObject()); } AccessibilityObject* AccessibilityRenderObject::parentObject() const { if (!m_renderer) - return 0; + return nullptr; if (ariaRoleAttribute() == MenuBarRole) return axObjectCache()->getOrCreate(m_renderer->parent()); @@ -498,15 +494,19 @@ AccessibilityObject* AccessibilityRenderObject::parentObject() const return parent; } + AXObjectCache* cache = axObjectCache(); + if (!cache) + return nullptr; + RenderObject* parentObj = renderParentObject(); if (parentObj) - return axObjectCache()->getOrCreate(parentObj); + return cache->getOrCreate(parentObj); // WebArea's parent should be the scroll view containing it. - if (isWebArea() || isSeamlessWebArea()) - return axObjectCache()->getOrCreate(&m_renderer->view().frameView()); + if (isWebArea()) + return cache->getOrCreate(&m_renderer->view().frameView()); - return 0; + return nullptr; } bool AccessibilityRenderObject::isAttachment() const @@ -522,34 +522,20 @@ bool AccessibilityRenderObject::isAttachment() const bool AccessibilityRenderObject::isFileUploadButton() const { - if (m_renderer && m_renderer->node() && isHTMLInputElement(m_renderer->node())) { - HTMLInputElement* input = toHTMLInputElement(m_renderer->node()); - return input->isFileUpload(); + if (m_renderer && is<HTMLInputElement>(m_renderer->node())) { + HTMLInputElement& input = downcast<HTMLInputElement>(*m_renderer->node()); + return input.isFileUpload(); } return false; } - -bool AccessibilityRenderObject::isReadOnly() const -{ - ASSERT(m_renderer); - - if (isWebArea()) { - if (HTMLElement* body = m_renderer->document().body()) { - if (body->hasEditableStyle()) - return false; - } - - return !m_renderer->document().hasEditableStyle(); - } - - return AccessibilityNodeObject::isReadOnly(); -} bool AccessibilityRenderObject::isOffScreen() const { - ASSERT(m_renderer); - IntRect contentRect = pixelSnappedIntRect(m_renderer->absoluteClippedOverflowRect()); + if (!m_renderer) + return true; + + IntRect contentRect = snappedIntRect(m_renderer->absoluteClippedOverflowRect()); // FIXME: unclear if we need LegacyIOSDocumentVisibleRect. IntRect viewRect = m_renderer->view().frameView().visibleContentRect(ScrollableArea::LegacyIOSDocumentVisibleRect); viewRect.intersect(contentRect); @@ -559,33 +545,34 @@ bool AccessibilityRenderObject::isOffScreen() const Element* AccessibilityRenderObject::anchorElement() const { if (!m_renderer) - return 0; + return nullptr; AXObjectCache* cache = axObjectCache(); - RenderObject* currRenderer; + if (!cache) + return nullptr; + + RenderObject* currentRenderer; // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though. - for (currRenderer = m_renderer; currRenderer && !currRenderer->node(); currRenderer = currRenderer->parent()) { - if (currRenderer->isAnonymousBlock()) { - RenderObject* continuation = toRenderBlock(currRenderer)->continuation(); - if (continuation) + for (currentRenderer = m_renderer; currentRenderer && !currentRenderer->node(); currentRenderer = currentRenderer->parent()) { + if (currentRenderer->isAnonymousBlock()) { + if (RenderObject* continuation = downcast<RenderBlock>(*currentRenderer).continuation()) return cache->getOrCreate(continuation)->anchorElement(); } } // bail if none found - if (!currRenderer) - return 0; + if (!currentRenderer) + return nullptr; // search up the DOM tree for an anchor element // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement - Node* node = currRenderer->node(); - for ( ; node; node = node->parentNode()) { - if (isHTMLAnchorElement(node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor())) - return toElement(node); + for (Node* node = currentRenderer->node(); node; node = node->parentNode()) { + if (is<HTMLAnchorElement>(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isLink())) + return downcast<Element>(node); } - return 0; + return nullptr; } String AccessibilityRenderObject::helpText() const @@ -602,21 +589,22 @@ String AccessibilityRenderObject::helpText() const return describedBy; String description = accessibilityDescription(); - for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { - if (curr->node() && curr->node()->isHTMLElement()) { - const AtomicString& summary = toElement(curr->node())->getAttribute(summaryAttr); + for (RenderObject* ancestor = m_renderer; ancestor; ancestor = ancestor->parent()) { + if (is<HTMLElement>(ancestor->node())) { + HTMLElement& element = downcast<HTMLElement>(*ancestor->node()); + const AtomicString& summary = element.getAttribute(summaryAttr); if (!summary.isEmpty()) return summary; // The title attribute should be used as help text unless it is already being used as descriptive text. - const AtomicString& title = toElement(curr->node())->getAttribute(titleAttr); + const AtomicString& title = element.getAttribute(titleAttr); if (!title.isEmpty() && description != title) return title; } // Only take help text from an ancestor element if its a group or an unknown role. If help was // added to those kinds of elements, it is likely it was meant for a child element. - AccessibilityObject* axObj = axObjectCache()->getOrCreate(curr); + AccessibilityObject* axObj = axObjectCache()->getOrCreate(ancestor); if (axObj) { AccessibilityRole role = axObj->roleValue(); if (role != GroupRole && role != UnknownRole) @@ -632,28 +620,25 @@ String AccessibilityRenderObject::textUnderElement(AccessibilityTextUnderElement if (!m_renderer) return String(); - if (m_renderer->isFileUploadControl()) - return toRenderFileUploadControl(m_renderer)->buttonValue(); + if (is<RenderFileUploadControl>(*m_renderer)) + return downcast<RenderFileUploadControl>(*m_renderer).buttonValue(); // Reflect when a content author has explicitly marked a line break. if (m_renderer->isBR()) return ASCIILiteral("\n"); -#if ENABLE(MATHML) - // Math operators create RenderText nodes on the fly that are not tied into the DOM in a reasonable way, - // so rangeOfContents does not work for them (nor does regular text selection). - if (m_renderer->isText() && isMathElement()) { - if (ancestorsOfType<RenderMathMLOperator>(*m_renderer).first()) - return toRenderText(*m_renderer).text(); - } -#endif + bool isRenderText = is<RenderText>(*m_renderer); + + if (shouldGetTextFromNode(mode)) + return AccessibilityNodeObject::textUnderElement(mode); // We use a text iterator for text objects AND for those cases where we are // explicitly asking for the full text under a given element. - if (m_renderer->isText() || mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren) { + bool shouldIncludeAllChildren = mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren; + if (isRenderText || shouldIncludeAllChildren) { // If possible, use a text iterator to get the text, so that whitespace // is handled consistently. - Document* nodeDocument = 0; + Document* nodeDocument = nullptr; RefPtr<Range> textRange; if (Node* node = m_renderer->node()) { nodeDocument = &node->document(); @@ -683,34 +668,65 @@ String AccessibilityRenderObject::textUnderElement(AccessibilityTextUnderElement // catch stale WebCoreAXObject (see <rdar://problem/3960196>) if (frame->document() != nodeDocument) return String(); + + // The tree should be stable before looking through the children of a non-Render Text object. + // Otherwise, further uses of TextIterator will force a layout update, potentially altering + // the accessibility tree and causing crashes in the loop that computes the result text. + ASSERT((isRenderText || !shouldIncludeAllChildren) || (!nodeDocument->renderView()->layoutState() && !nodeDocument->childNeedsStyleRecalc())); + return plainText(textRange.get(), textIteratorBehaviorForTextRange()); } } // Sometimes text fragments don't have Nodes associated with them (like when // CSS content is used to insert text or when a RenderCounter is used.) - if (m_renderer->isText()) { - RenderText* renderTextObject = toRenderText(m_renderer); - if (renderTextObject->isTextFragment()) { - + if (is<RenderText>(*m_renderer)) { + RenderText& renderTextObject = downcast<RenderText>(*m_renderer); + if (is<RenderTextFragment>(renderTextObject)) { + RenderTextFragment& renderTextFragment = downcast<RenderTextFragment>(renderTextObject); // The alt attribute may be set on a text fragment through CSS, which should be honored. - const String& altText = toRenderTextFragment(renderTextObject)->altText(); + const String& altText = renderTextFragment.altText(); if (!altText.isEmpty()) return altText; - return String(static_cast<RenderTextFragment*>(m_renderer)->contentString()); + return renderTextFragment.contentString(); } - return String(renderTextObject->text()); + return renderTextObject.text(); } } return AccessibilityNodeObject::textUnderElement(mode); } +bool AccessibilityRenderObject::shouldGetTextFromNode(AccessibilityTextUnderElementMode mode) const +{ + if (!m_renderer) + return false; + + // AccessibilityRenderObject::textUnderElement() gets the text of anonymous blocks by using + // the child nodes to define positions. CSS tables and their anonymous descendants lack + // children with nodes. + if (m_renderer->isAnonymous() && m_renderer->isTablePart()) + return mode.childrenInclusion == AccessibilityTextUnderElementMode::TextUnderElementModeIncludeAllChildren; + + // AccessibilityRenderObject::textUnderElement() calls rangeOfContents() to create the text + // range. rangeOfContents() does not include CSS-generated content. + if (m_renderer->isBeforeOrAfterContent()) + return true; + if (Node* node = m_renderer->node()) { + Node* firstChild = node->pseudoAwareFirstChild(); + Node* lastChild = node->pseudoAwareLastChild(); + if ((firstChild && firstChild->isPseudoElement()) || (lastChild && lastChild->isPseudoElement())) + return true; + } + + return false; +} + Node* AccessibilityRenderObject::node() const { if (!m_renderer) - return 0; + return nullptr; if (m_renderer->isRenderView()) return &m_renderer->document(); return m_renderer->node(); @@ -733,25 +749,25 @@ String AccessibilityRenderObject::stringValue() const return staticText; } - if (m_renderer->isText()) + if (is<RenderText>(*m_renderer)) return textUnderElement(); - - if (cssBox && cssBox->isMenuList()) { + + if (is<RenderMenuList>(cssBox)) { // RenderMenuList will go straight to the text() of its selected item. // This has to be overridden in the case where the selected item has an ARIA label. - HTMLSelectElement* selectElement = toHTMLSelectElement(m_renderer->node()); - int selectedIndex = selectElement->selectedIndex(); - const Vector<HTMLElement*> listItems = selectElement->listItems(); + HTMLSelectElement& selectElement = downcast<HTMLSelectElement>(*m_renderer->node()); + int selectedIndex = selectElement.selectedIndex(); + const Vector<HTMLElement*>& listItems = selectElement.listItems(); if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) { - const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr); + const AtomicString& overriddenDescription = listItems[selectedIndex]->attributeWithoutSynchronization(aria_labelAttr); if (!overriddenDescription.isNull()) return overriddenDescription; } - return toRenderMenuList(m_renderer)->text(); + return downcast<RenderMenuList>(*m_renderer).text(); } - if (m_renderer->isListMarker()) - return toRenderListMarker(*m_renderer).text(); + if (is<RenderListMarker>(*m_renderer)) + return downcast<RenderListMarker>(*m_renderer).text(); if (isWebArea()) return String(); @@ -759,8 +775,13 @@ String AccessibilityRenderObject::stringValue() const if (isTextControl()) return text(); - if (m_renderer->isFileUploadControl()) - return toRenderFileUploadControl(m_renderer)->fileTextValue(); +#if PLATFORM(IOS) + if (isInputTypePopupButton()) + return textUnderElement(); +#endif + + if (is<RenderFileUploadControl>(*m_renderer)) + return downcast<RenderFileUploadControl>(*m_renderer).fileTextValue(); // FIXME: We might need to implement a value here for more types // FIXME: It would be better not to advertise a value at all for the types for which we don't implement one; @@ -772,19 +793,19 @@ String AccessibilityRenderObject::stringValue() const HTMLLabelElement* AccessibilityRenderObject::labelElementContainer() const { if (!m_renderer) - return 0; + return nullptr; // the control element should not be considered part of the label if (isControl()) - return 0; + return nullptr; // find if this has a parent that is a label for (Node* parentNode = m_renderer->node(); parentNode; parentNode = parentNode->parentNode()) { - if (isHTMLLabelElement(parentNode)) - return toHTMLLabelElement(parentNode); + if (is<HTMLLabelElement>(*parentNode)) + return downcast<HTMLLabelElement>(parentNode); } - return 0; + return nullptr; } // The boundingBox for elements within the remote SVG element needs to be offset by its position @@ -814,27 +835,25 @@ LayoutRect AccessibilityRenderObject::boundingBoxRect() const // We should also use absoluteQuads for SVG elements, otherwise transforms won't be applied. Vector<FloatQuad> quads; bool isSVGRoot = false; -#if ENABLE(SVG) + if (obj->isSVGRoot()) isSVGRoot = true; -#endif - if (obj->isText()) - quads = toRenderText(obj)->absoluteQuadsClippedToEllipsis(); - else if (isWebArea() || isSeamlessWebArea() || isSVGRoot) + + if (is<RenderText>(*obj)) + quads = downcast<RenderText>(*obj).absoluteQuadsClippedToEllipsis(); + else if (isWebArea() || isSVGRoot) obj->absoluteQuads(quads); else obj->absoluteFocusRingQuads(quads); LayoutRect result = boundingBoxForQuads(obj, quads); -#if ENABLE(SVG) Document* document = this->document(); if (document && document->isSVGDocument()) offsetBoundingBoxForRemoteSVGElement(result); -#endif // The size of the web area should be the content size, not the clipped size. - if (isWebArea() || isSeamlessWebArea()) + if (isWebArea()) result.setSize(obj->view().frameView().contentsSize()); return result; @@ -845,7 +864,7 @@ LayoutRect AccessibilityRenderObject::checkboxOrRadioRect() const if (!m_renderer) return LayoutRect(); - HTMLLabelElement* label = labelForElement(toElement(m_renderer->node())); + HTMLLabelElement* label = labelForElement(downcast<Element>(m_renderer->node())); if (!label || !label->renderer()) return boundingBoxRect(); @@ -865,19 +884,13 @@ LayoutRect AccessibilityRenderObject::elementRect() const bool AccessibilityRenderObject::supportsPath() const { -#if ENABLE(SVG) - if (m_renderer && m_renderer->isSVGShape()) - return true; -#endif - - return false; + return is<RenderSVGShape>(m_renderer); } Path AccessibilityRenderObject::elementPath() const { -#if ENABLE(SVG) - if (m_renderer && m_renderer->isSVGShape() && toRenderSVGShape(m_renderer)->hasPath()) { - Path path = toRenderSVGShape(m_renderer)->path(); + if (is<RenderSVGShape>(m_renderer) && downcast<RenderSVGShape>(*m_renderer).hasPath()) { + Path path = downcast<RenderSVGShape>(*m_renderer).path(); // The SVG path is in terms of the parent's bounding box. The path needs to be offset to frame coordinates. if (auto svgRoot = ancestorsOfType<RenderSVGRoot>(*m_renderer).first()) { @@ -887,7 +900,6 @@ Path AccessibilityRenderObject::elementPath() const return path; } -#endif return Path(); } @@ -899,13 +911,13 @@ IntPoint AccessibilityRenderObject::clickPoint() return children()[0]->clickPoint(); // use the default position unless this is an editable web area, in which case we use the selection bounds. - if (!isWebArea() || isReadOnly()) + if (!isWebArea() || !canSetValueAttribute()) return AccessibilityObject::clickPoint(); VisibleSelection visSelection = selection(); VisiblePositionRange range = VisiblePositionRange(visSelection.visibleStart(), visSelection.visibleEnd()); IntRect bounds = boundsForVisiblePositionRange(range); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) bounds.setLocation(m_renderer->view().frameView().screenToContents(bounds.location())); #endif return IntPoint(bounds.x() + (bounds.width() / 2), bounds.y() - (bounds.height() / 2)); @@ -914,27 +926,24 @@ IntPoint AccessibilityRenderObject::clickPoint() AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const { Element* element = anchorElement(); - if (!element) - return 0; - // Right now, we do not support ARIA links as internal link elements - if (!isHTMLAnchorElement(element)) - return 0; - HTMLAnchorElement* anchor = toHTMLAnchorElement(element); + if (!is<HTMLAnchorElement>(element)) + return nullptr; + HTMLAnchorElement& anchor = downcast<HTMLAnchorElement>(*element); - URL linkURL = anchor->href(); + URL linkURL = anchor.href(); String fragmentIdentifier = linkURL.fragmentIdentifier(); if (fragmentIdentifier.isEmpty()) - return 0; + return nullptr; // check if URL is the same as current URL URL documentURL = m_renderer->document().url(); if (!equalIgnoringFragmentIdentifier(documentURL, linkURL)) - return 0; + return nullptr; Node* linkedNode = m_renderer->document().findAnchor(fragmentIdentifier); if (!linkedNode) - return 0; + return nullptr; // The element we find may not be accessible, so find the first accessible object. return firstAccessibleObjectFromNode(linkedNode); @@ -948,36 +957,33 @@ ESpeak AccessibilityRenderObject::speakProperty() const return m_renderer->style().speak(); } -void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const +void AccessibilityRenderObject::addRadioButtonGroupChildren(AccessibilityObject* parent, AccessibilityChildrenVector& linkedUIElements) const { - if (!m_renderer || roleValue() != RadioButtonRole) - return; + for (const auto& child : parent->children()) { + if (child->roleValue() == RadioButtonRole) + linkedUIElements.append(child); + else + addRadioButtonGroupChildren(child.get(), linkedUIElements); + } +} - Node* node = m_renderer->node(); - if (!node || !isHTMLInputElement(node)) +void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const +{ + if (roleValue() != RadioButtonRole) return; - HTMLInputElement* input = toHTMLInputElement(node); - // if there's a form, then this is easy - if (input->form()) { - Vector<Ref<Element>> formElements; - input->form()->getNamedElements(input->name(), formElements); - - for (auto& associateElement : formElements) { - if (AccessibilityObject* object = axObjectCache()->getOrCreate(&associateElement.get())) - linkedUIElements.append(object); - } + Node* node = this->node(); + if (is<HTMLInputElement>(node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + for (auto& radioSibling : input.radioButtonGroup()) { + if (AccessibilityObject* object = axObjectCache()->getOrCreate(radioSibling)) + linkedUIElements.append(object); + } } else { - RefPtr<NodeList> list = node->document().getElementsByTagName("input"); - unsigned len = list->length(); - for (unsigned i = 0; i < len; ++i) { - if (isHTMLInputElement(list->item(i))) { - HTMLInputElement* associateElement = toHTMLInputElement(list->item(i)); - if (associateElement->isRadioButton() && associateElement->name() == input->name()) { - if (AccessibilityObject* object = axObjectCache()->getOrCreate(associateElement)) - linkedUIElements.append(object); - } - } + // If we didn't find any radio button siblings with the traditional naming, lets search for a radio group role and find its children. + for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { + if (parent->roleValue() == RadioGroupRole) + addRadioButtonGroupChildren(parent, linkedUIElements); } } } @@ -988,7 +994,7 @@ void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& li { ariaFlowToElements(linkedUIElements); - if (isAnchor()) { + if (isLink()) { AccessibilityObject* linkedAXElement = internalLinkElement(); if (linkedAXElement) linkedUIElements.append(linkedAXElement); @@ -1010,42 +1016,6 @@ bool AccessibilityRenderObject::ariaHasPopup() const return elementAttributeValue(aria_haspopupAttr); } -bool AccessibilityRenderObject::supportsARIAFlowTo() const -{ - return !getAttribute(aria_flowtoAttr).isEmpty(); -} - -void AccessibilityRenderObject::ariaFlowToElements(AccessibilityChildrenVector& flowTo) const -{ - Vector<Element*> elements; - elementsFromAttribute(elements, aria_flowtoAttr); - - AXObjectCache* cache = axObjectCache(); - for (const auto& element : elements) { - AccessibilityObject* flowToElement = cache->getOrCreate(element); - if (flowToElement) - flowTo.append(flowToElement); - } - -} - -bool AccessibilityRenderObject::supportsARIADescribedBy() const -{ - return !getAttribute(aria_describedbyAttr).isEmpty(); -} - -void AccessibilityRenderObject::ariaDescribedByElements(AccessibilityChildrenVector& ariaDescribedBy) const -{ - Vector<Element*> elements; - elementsFromAttribute(elements, aria_describedbyAttr); - - AXObjectCache* cache = axObjectCache(); - for (const auto& element : elements) { - if (AccessibilityObject* describedByElement = cache->getOrCreate(element)) - ariaDescribedBy.append(describedByElement); - } -} - bool AccessibilityRenderObject::supportsARIADropping() const { const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr); @@ -1055,7 +1025,7 @@ bool AccessibilityRenderObject::supportsARIADropping() const bool AccessibilityRenderObject::supportsARIADragging() const { const AtomicString& grabbed = getAttribute(aria_grabbedAttr); - return equalIgnoringCase(grabbed, "true") || equalIgnoringCase(grabbed, "false"); + return equalLettersIgnoringASCIICase(grabbed, "true") || equalLettersIgnoringASCIICase(grabbed, "false"); } bool AccessibilityRenderObject::isARIAGrabbed() @@ -1078,7 +1048,7 @@ void AccessibilityRenderObject::determineARIADropEffects(Vector<String>& effects bool AccessibilityRenderObject::exposesTitleUIElement() const { - if (!isControl()) + if (!isControl() && !isFigure()) return false; // If this control is ignored (because it's invisible), @@ -1090,26 +1060,42 @@ bool AccessibilityRenderObject::exposesTitleUIElement() const if (hasTextAlternative()) return false; + // When <label> element has aria-label or aria-labelledby on it, we shouldn't expose it as the + // titleUIElement, otherwise its inner text will be announced by a screenreader. + if (isLabelable()) { + if (HTMLLabelElement* label = labelForElement(downcast<Element>(node()))) { + if (!label->attributeWithoutSynchronization(aria_labelAttr).isEmpty()) + return false; + if (AccessibilityObject* labelObject = axObjectCache()->getOrCreate(label)) { + if (!labelObject->ariaLabeledByAttribute().isEmpty()) + return false; + } + } + } + return true; } AccessibilityObject* AccessibilityRenderObject::titleUIElement() const { if (!m_renderer) - return 0; + return nullptr; // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset if (isFieldset()) - return axObjectCache()->getOrCreate(toRenderFieldset(m_renderer)->findLegend(RenderFieldset::IncludeFloatingOrOutOfFlow)); + return axObjectCache()->getOrCreate(downcast<RenderFieldset>(*m_renderer).findLegend(RenderFieldset::IncludeFloatingOrOutOfFlow)); + + if (isFigure()) + return captionForFigure(); Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) - return 0; - HTMLLabelElement* label = labelForElement(toElement(node)); + if (!is<Element>(node)) + return nullptr; + HTMLLabelElement* label = labelForElement(downcast<Element>(node)); if (label && label->renderer()) return axObjectCache()->getOrCreate(label); - return 0; + return nullptr; } bool AccessibilityRenderObject::isAllowedChildOfTree() const @@ -1117,7 +1103,10 @@ bool AccessibilityRenderObject::isAllowedChildOfTree() const // Determine if this is in a tree. If so, we apply special behavior to make it work like an AXOutline. AccessibilityObject* axObj = parentObject(); bool isInTree = false; + bool isTreeItemDescendant = false; while (axObj) { + if (axObj->roleValue() == TreeItemRole) + isTreeItemDescendant = true; if (axObj->isTree()) { isInTree = true; break; @@ -1128,7 +1117,7 @@ bool AccessibilityRenderObject::isAllowedChildOfTree() const // If the object is in a tree, only tree items should be exposed (and the children of tree items). if (isInTree) { AccessibilityRole role = roleValue(); - if (role != TreeItemRole && role != StaticTextRole) + if (role != TreeItemRole && role != StaticTextRole && !isTreeItemDescendant) return false; } return true; @@ -1156,7 +1145,7 @@ AccessibilityObjectInclusion AccessibilityRenderObject::defaultObjectInclusion() if (m_renderer->style().visibility() != VISIBLE) { // aria-hidden is meant to override visibility as the determinant in AX hierarchy inclusion. - if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "false")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_hiddenAttr), "false")) return DefaultBehavior; return IgnoreObject; @@ -1171,6 +1160,9 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const ASSERT(m_initialized); #endif + if (!m_renderer) + return true; + // Check first if any of the common reasons cause this element to be ignored. // Then process other use cases that need to be applied to all the various roles // that AccessibilityRenderObjects take on. @@ -1199,7 +1191,7 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const return accessibilityIgnoreAttachment(); // ignore popup menu items because AppKit does - if (ancestorsOfType<RenderMenuList>(*m_renderer).first()) + if (m_renderer && ancestorsOfType<RenderMenuList>(*m_renderer).first()) return true; // find out if this element is inside of a label element. @@ -1207,16 +1199,20 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const AccessibilityObject* controlObject = correspondingControlForLabelElement(); if (controlObject && !controlObject->exposesTitleUIElement() && controlObject->isCheckboxOrRadio()) return true; + + // https://webkit.org/b/161276 Getting the controlObject might cause the m_renderer to be nullptr. + if (!m_renderer) + return true; if (m_renderer->isBR()) return true; - if (m_renderer->isText()) { + if (is<RenderText>(*m_renderer)) { // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level AccessibilityObject* parent = parentObjectUnignored(); if (parent && (parent->isMenuItem() || parent->ariaRoleAttribute() == MenuButtonRole)) return true; - auto& renderText = toRenderText(*m_renderer); + auto& renderText = downcast<RenderText>(*m_renderer); if (!renderText.hasRenderedText()) return true; @@ -1226,9 +1222,13 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const return true; } + // Walking up the parent chain might reset the m_renderer. + if (!m_renderer) + return true; + // The alt attribute may be set on a text fragment through CSS, which should be honored. - if (renderText.isTextFragment()) { - AccessibilityObjectInclusion altTextInclusion = objectInclusionFromAltText(toRenderTextFragment(&renderText)->altText()); + if (is<RenderTextFragment>(renderText)) { + AccessibilityObjectInclusion altTextInclusion = objectInclusionFromAltText(downcast<RenderTextFragment>(renderText).altText()); if (altTextInclusion == IgnoreObject) return true; if (altTextInclusion == IncludeObject) @@ -1245,16 +1245,39 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (isLink()) return false; + if (isLandmark()) + return false; + // all controls are accessible if (isControl()) return false; - if (ariaRoleAttribute() != UnknownRole) + if (isFigure()) return false; + switch (roleValue()) { + case AudioRole: + case DescriptionListTermRole: + case DescriptionListDetailRole: + case DetailsRole: + case DocumentArticleRole: + case LandmarkRegionRole: + case ListItemRole: + case VideoRole: + return false; + default: + break; + } + + if (ariaRoleAttribute() != UnknownRole) + return false; + + if (roleValue() == HorizontalRuleRole) + return false; + // don't ignore labels, because they serve as TitleUIElements Node* node = m_renderer->node(); - if (node && isHTMLLabelElement(node)) + if (is<HTMLLabelElement>(node)) return false; // Anything that is content editable should not be ignored. @@ -1263,14 +1286,6 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (hasContentEditableAttributeSet()) return false; - switch (roleValue()) { - case AudioRole: - case ListItemRole: - case VideoRole: - return false; - default: - break; - } // if this element has aria attributes on it, it should not be ignored. if (supportsARIAAttributes()) @@ -1285,8 +1300,8 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const return false; #endif - if (m_renderer->isRenderBlockFlow() && m_renderer->childrenInline() && !canSetFocusAttribute()) - return !toRenderBlockFlow(m_renderer)->hasLines() && !mouseButtonListener(); + if (is<RenderBlockFlow>(*m_renderer) && m_renderer->childrenInline() && !canSetFocusAttribute()) + return !downcast<RenderBlockFlow>(*m_renderer).hasLines() && !mouseButtonListener(); // ignore images seemingly used as spacers if (isImage()) { @@ -1298,9 +1313,9 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const // First check the RenderImage's altText (which can be set through a style sheet, or come from the Element). // However, if this is not a native image, fallback to the attribute on the Element. AccessibilityObjectInclusion altTextInclusion = DefaultBehavior; - bool isRenderImage = m_renderer && m_renderer->isRenderImage(); + bool isRenderImage = is<RenderImage>(m_renderer); if (isRenderImage) - altTextInclusion = objectInclusionFromAltText(toRenderImage(m_renderer)->altText()); + altTextInclusion = objectInclusionFromAltText(downcast<RenderImage>(*m_renderer).altText()); else altTextInclusion = objectInclusionFromAltText(getAttribute(altAttr).string()); @@ -1315,13 +1330,13 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (isRenderImage) { // check for one-dimensional image - RenderImage* image = toRenderImage(m_renderer); - if (image->height() <= 1 || image->width() <= 1) + RenderImage& image = downcast<RenderImage>(*m_renderer); + if (image.height() <= 1 || image.width() <= 1) return true; // check whether rendered image was stretched from one-dimensional file image - if (image->cachedImage()) { - LayoutSize imageSize = image->cachedImage()->imageSizeForRenderer(toRenderElement(m_renderer), image->view().zoomFactor()); + if (image.cachedImage()) { + LayoutSize imageSize = image.cachedImage()->imageSizeForRenderer(&image, image.view().zoomFactor()); return imageSize.height() <= 1 || imageSize.width() <= 1; } } @@ -1332,17 +1347,31 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (canvasHasFallbackContent()) return false; - if (m_renderer->isBox()) { - RenderBox* canvasBox = toRenderBox(m_renderer); - if (canvasBox->height() <= 1 || canvasBox->width() <= 1) + if (is<RenderBox>(*m_renderer)) { + auto& canvasBox = downcast<RenderBox>(*m_renderer); + if (canvasBox.height() <= 1 || canvasBox.width() <= 1) return true; } // Otherwise fall through; use presence of help text, title, or description to decide. } - if (isWebArea() || isSeamlessWebArea() || m_renderer->isListMarker()) + if (m_renderer->isListMarker()) { + AccessibilityObject* parent = parentObjectUnignored(); + return parent && !parent->isListItem(); + } + + if (isWebArea()) return false; +#if ENABLE(METER_ELEMENT) + // The render tree of meter includes a RenderBlock (meter) and a RenderMeter (div). + // We expose the latter and thus should ignore the former. However, if the author + // includes a title attribute on the element, hasAttributesRequiredForInclusion() + // will return true, potentially resulting in a redundant accessible object. + if (is<HTMLMeterElement>(node)) + return true; +#endif + // Using the presence of an accessible name to decide an element's visibility is not // as definitive as previous checks, so this should remain as one of the last. if (hasAttributesRequiredForInclusion()) @@ -1366,7 +1395,14 @@ bool AccessibilityRenderObject::computeAccessibilityIsIgnored() const if (node && node->hasTagName(dfnTag)) return false; - // By default, objects should be ignored so that the AX hierarchy is not + if (isStyleFormatGroup()) + return false; + + // Make sure that ruby containers are not ignored. + if (m_renderer->isRubyRun() || m_renderer->isRubyBlock() || m_renderer->isRubyInline()) + return false; + + // By default, objects should be ignored so that the AX hierarchy is not // filled with unnecessary items. return true; } @@ -1384,18 +1420,14 @@ double AccessibilityRenderObject::estimatedLoadingProgress() const if (isLoaded()) return 1.0; - Page* page = m_renderer->document().page(); - if (!page) - return 0; - - return page->progress().estimatedProgress(); + return m_renderer->page().progress().estimatedProgress(); } int AccessibilityRenderObject::layoutCount() const { - if (!m_renderer->isRenderView()) + if (!is<RenderView>(*m_renderer)) return 0; - return toRenderView(*m_renderer).frameView().layoutCount(); + return downcast<RenderView>(*m_renderer).frameView().layoutCount(); } String AccessibilityRenderObject::text() const @@ -1416,20 +1448,27 @@ int AccessibilityRenderObject::textLength() const return text().length(); } -PlainTextRange AccessibilityRenderObject::ariaSelectedTextRange() const +PlainTextRange AccessibilityRenderObject::documentBasedSelectedTextRange() const { Node* node = m_renderer->node(); if (!node) return PlainTextRange(); - + VisibleSelection visibleSelection = selection(); RefPtr<Range> currentSelectionRange = visibleSelection.toNormalizedRange(); - if (!currentSelectionRange || !currentSelectionRange->intersectsNode(node, IGNORE_EXCEPTION)) + if (!currentSelectionRange) return PlainTextRange(); - + // FIXME: The reason this does the correct thing when the selection is in the + // shadow tree of an input element is that we get an exception below, and we + // choose to interpret all exceptions as "does not intersect". Seems likely + // that does not handle all cases correctly. + auto intersectsResult = currentSelectionRange->intersectsNode(*node); + if (!intersectsResult.hasException() && !intersectsResult.releaseReturnValue()) + return PlainTextRange(); + int start = indexForVisiblePosition(visibleSelection.start()); int end = indexForVisiblePosition(visibleSelection.end()); - + return PlainTextRange(start, end - start); } @@ -1441,24 +1480,19 @@ String AccessibilityRenderObject::selectedText() const return String(); // need to return something distinct from empty string if (isNativeTextControl()) { - HTMLTextFormControlElement& textControl = toRenderTextControl(m_renderer)->textFormControlElement(); + HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement(); return textControl.selectedText(); } - if (ariaRoleAttribute() == UnknownRole) - return String(); - - return doAXStringForRange(ariaSelectedTextRange()); + return doAXStringForRange(documentBasedSelectedTextRange()); } const AtomicString& AccessibilityRenderObject::accessKey() const { Node* node = m_renderer->node(); - if (!node) - return nullAtom; - if (!node->isElementNode()) + if (!is<Element>(node)) return nullAtom; - return toElement(node)->getAttribute(accesskeyAttr); + return downcast<Element>(*node).getAttribute(accesskeyAttr); } VisibleSelection AccessibilityRenderObject::selection() const @@ -1474,57 +1508,82 @@ PlainTextRange AccessibilityRenderObject::selectedTextRange() const return PlainTextRange(); AccessibilityRole ariaRole = ariaRoleAttribute(); - if (isNativeTextControl() && ariaRole == UnknownRole) { - HTMLTextFormControlElement& textControl = toRenderTextControl(m_renderer)->textFormControlElement(); + // Use the text control native range if it's a native object and it has no ARIA role (or has a text based ARIA role). + if (isNativeTextControl() && (ariaRole == UnknownRole || isARIATextControl())) { + HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement(); return PlainTextRange(textControl.selectionStart(), textControl.selectionEnd() - textControl.selectionStart()); } - if (ariaRole == UnknownRole) - return PlainTextRange(); - - return ariaSelectedTextRange(); + return documentBasedSelectedTextRange(); +} + +static void setTextSelectionIntent(AXObjectCache* cache, AXTextStateChangeType type) +{ + if (!cache) + return; + AXTextStateChangeIntent intent(type, AXTextSelection { AXTextSelectionDirectionDiscontiguous, AXTextSelectionGranularityUnknown, false }); + cache->setTextSelectionIntent(intent); + cache->setIsSynchronizingSelection(true); +} + +static void clearTextSelectionIntent(AXObjectCache* cache) +{ + if (!cache) + return; + cache->setTextSelectionIntent(AXTextStateChangeIntent()); + cache->setIsSynchronizingSelection(false); } void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range) { + setTextSelectionIntent(axObjectCache(), range.length ? AXTextStateChangeTypeSelectionExtend : AXTextStateChangeTypeSelectionMove); + if (isNativeTextControl()) { - HTMLTextFormControlElement& textControl = toRenderTextControl(m_renderer)->textFormControlElement(); + HTMLTextFormControlElement& textControl = downcast<RenderTextControl>(*m_renderer).textFormControlElement(); textControl.setSelectionRange(range.start, range.start + range.length); - return; + } else { + ASSERT(node()); + VisiblePosition start = visiblePositionForIndexUsingCharacterIterator(*node(), range.start); + VisiblePosition end = visiblePositionForIndexUsingCharacterIterator(*node(), range.start + range.length); + m_renderer->frame().selection().setSelection(VisibleSelection(start, end), FrameSelection::defaultSetSelectionOptions(UserTriggered)); } - - Node* node = m_renderer->node(); - m_renderer->frame().selection().setSelection(VisibleSelection(Position(node, range.start, Position::PositionIsOffsetInAnchor), - Position(node, range.start + range.length, Position::PositionIsOffsetInAnchor), DOWNSTREAM)); + + clearTextSelectionIntent(axObjectCache()); } URL AccessibilityRenderObject::url() const { - if (isAnchor() && isHTMLAnchorElement(m_renderer->node())) { - if (HTMLAnchorElement* anchor = toHTMLAnchorElement(anchorElement())) + if (isLink() && is<HTMLAnchorElement>(*m_renderer->node())) { + if (HTMLAnchorElement* anchor = downcast<HTMLAnchorElement>(anchorElement())) return anchor->href(); } if (isWebArea()) return m_renderer->document().url(); - if (isImage() && m_renderer->node() && isHTMLImageElement(m_renderer->node())) - return toHTMLImageElement(m_renderer->node())->src(); + if (isImage() && is<HTMLImageElement>(m_renderer->node())) + return downcast<HTMLImageElement>(*m_renderer->node()).src(); if (isInputImage()) - return toHTMLInputElement(m_renderer->node())->src(); + return downcast<HTMLInputElement>(*m_renderer->node()).src(); return URL(); } bool AccessibilityRenderObject::isUnvisited() const { + if (!m_renderer) + return true; + // FIXME: Is it a privacy violation to expose unvisited information to accessibility APIs? return m_renderer->style().isLink() && m_renderer->style().insideLink() == InsideUnvisitedLink; } bool AccessibilityRenderObject::isVisited() const { + if (!m_renderer) + return false; + // FIXME: Is it a privacy violation to expose visited information to accessibility APIs? return m_renderer->style().isLink() && m_renderer->style().insideLink() == InsideVisitedLink; } @@ -1535,11 +1594,10 @@ void AccessibilityRenderObject::setElementAttributeValue(const QualifiedName& at return; Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return; - Element* element = toElement(node); - element->setAttribute(attributeName, (value) ? "true" : "false"); + downcast<Element>(*node).setAttribute(attributeName, (value) ? "true" : "false"); } bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attributeName) const @@ -1547,7 +1605,7 @@ bool AccessibilityRenderObject::elementAttributeValue(const QualifiedName& attri if (!m_renderer) return false; - return equalIgnoringCase(getAttribute(attributeName), "true"); + return equalLettersIgnoringASCIICase(getAttribute(attributeName), "true"); } bool AccessibilityRenderObject::isSelected() const @@ -1555,17 +1613,19 @@ bool AccessibilityRenderObject::isSelected() const if (!m_renderer) return false; - Node* node = m_renderer->node(); - if (!node) + if (!m_renderer->node()) return false; - const AtomicString& ariaSelected = getAttribute(aria_selectedAttr); - if (equalIgnoringCase(ariaSelected, "true")) + if (equalLettersIgnoringASCIICase(getAttribute(aria_selectedAttr), "true")) return true; if (isTabItem() && isTabItemSelected()) return true; + // Menu items are considered selectable by assistive technologies + if (isMenuItem()) + return isFocused() || parentObjectUnignored()->activeDescendant() == this; + return false; } @@ -1588,8 +1648,12 @@ bool AccessibilityRenderObject::isTabItemSelected() const Vector<Element*> elements; elementsFromAttribute(elements, aria_controlsAttr); + AXObjectCache* cache = axObjectCache(); + if (!cache) + return false; + for (const auto& element : elements) { - AccessibilityObject* tabPanel = axObjectCache()->getOrCreate(element); + AccessibilityObject* tabPanel = cache->getOrCreate(element); // A tab item should only control tab panels. if (!tabPanel || tabPanel->roleValue() != TabPanelRole) @@ -1635,25 +1699,35 @@ void AccessibilityRenderObject::setFocused(bool on) Document* document = this->document(); Node* node = this->node(); - if (!on || !node || !node->isElementNode()) { - document->setFocusedElement(0); + if (!on || !is<Element>(node)) { + document->setFocusedElement(nullptr); return; } + // When a node is told to set focus, that can cause it to be deallocated, which means that doing + // anything else inside this object will crash. To fix this, we added a RefPtr to protect this object + // long enough for duration. + RefPtr<AccessibilityObject> protectedThis(this); + // If this node is already the currently focused node, then calling focus() won't do anything. // That is a problem when focus is removed from the webpage to chrome, and then returns. // In these cases, we need to do what keyboard and mouse focus do, which is reset focus first. if (document->focusedElement() == node) - document->setFocusedElement(0); + document->setFocusedElement(nullptr); - toElement(node)->focus(); + // If we return from setFocusedElement and our element has been removed from a tree, axObjectCache() may be null. + if (AXObjectCache* cache = axObjectCache()) { + cache->setIsSynchronizingSelection(true); + downcast<Element>(*node).focus(); + cache->setIsSynchronizingSelection(false); + } } void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& selectedRows) { // Setting selected only makes sense in trees and tables (and tree-tables). AccessibilityRole role = roleValue(); - if (role != TreeRole && role != TreeGridRole && role != TableRole) + if (role != TreeRole && role != TreeGridRole && role != TableRole && role != GridRole) return; bool isMulti = isMultiSelectable(); @@ -1667,35 +1741,19 @@ void AccessibilityRenderObject::setSelectedRows(AccessibilityChildrenVector& sel void AccessibilityRenderObject::setValue(const String& string) { - if (!m_renderer || !m_renderer->node() || !m_renderer->node()->isElementNode()) + if (!m_renderer || !is<Element>(m_renderer->node())) return; - Element* element = toElement(m_renderer->node()); + Element& element = downcast<Element>(*m_renderer->node()); - if (!m_renderer->isBoxModelObject()) + if (!is<RenderBoxModelObject>(*m_renderer)) return; - RenderBoxModelObject* renderer = toRenderBoxModelObject(m_renderer); + RenderBoxModelObject& renderer = downcast<RenderBoxModelObject>(*m_renderer); // FIXME: Do we want to do anything here for ARIA textboxes? - if (renderer->isTextField()) { - // FIXME: This is not safe! Other elements could have a TextField renderer. - toHTMLInputElement(element)->setValue(string); - } else if (renderer->isTextArea()) { - // FIXME: This is not safe! Other elements could have a TextArea renderer. - toHTMLTextAreaElement(element)->setValue(string); - } -} - -void AccessibilityRenderObject::ariaOwnsElements(AccessibilityChildrenVector& axObjects) const -{ - Vector<Element*> elements; - elementsFromAttribute(elements, aria_ownsAttr); - - for (const auto& element : elements) { - RenderObject* render = element->renderer(); - AccessibilityObject* obj = axObjectCache()->getOrCreate(render); - if (obj) - axObjects.append(obj); - } + if (renderer.isTextField() && is<HTMLInputElement>(element)) + downcast<HTMLInputElement>(element).setValue(string); + else if (renderer.isTextArea() && is<HTMLTextAreaElement>(element)) + downcast<HTMLTextAreaElement>(element).setValue(string); } bool AccessibilityRenderObject::supportsARIAOwns() const @@ -1711,7 +1769,7 @@ RenderView* AccessibilityRenderObject::topRenderer() const { Document* topDoc = topDocument(); if (!topDoc) - return 0; + return nullptr; return topDoc->renderView(); } @@ -1719,51 +1777,53 @@ RenderView* AccessibilityRenderObject::topRenderer() const Document* AccessibilityRenderObject::document() const { if (!m_renderer) - return 0; + return nullptr; return &m_renderer->document(); } Widget* AccessibilityRenderObject::widget() const { - if (!m_renderer->isBoxModelObject() || !toRenderBoxModelObject(m_renderer)->isWidget()) - return 0; - return toRenderWidget(m_renderer)->widget(); + if (!is<RenderWidget>(*m_renderer)) + return nullptr; + return downcast<RenderWidget>(*m_renderer).widget(); } AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const { // find an image that is using this map if (!map) - return 0; + return nullptr; HTMLImageElement* imageElement = map->imageElement(); if (!imageElement) - return 0; + return nullptr; + + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(imageElement); - return axObjectCache()->getOrCreate(imageElement); + return nullptr; } void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result) { Document& document = m_renderer->document(); - RefPtr<HTMLCollection> links = document.links(); - for (unsigned i = 0; Node* curr = links->item(i); i++) { - RenderObject* obj = curr->renderer(); - if (obj) { - RefPtr<AccessibilityObject> axobj = document.axObjectCache()->getOrCreate(obj); - ASSERT(axobj); - if (!axobj->accessibilityIsIgnored() && axobj->isLink()) - result.append(axobj); + Ref<HTMLCollection> links = document.links(); + for (unsigned i = 0; auto* current = links->item(i); ++i) { + if (auto* renderer = current->renderer()) { + RefPtr<AccessibilityObject> axObject = document.axObjectCache()->getOrCreate(renderer); + ASSERT(axObject); + if (!axObject->accessibilityIsIgnored() && axObject->isLink()) + result.append(axObject); } else { - Node* parent = curr->parentNode(); - if (parent && isHTMLAreaElement(curr) && isHTMLMapElement(parent)) { - AccessibilityImageMapLink* areaObject = toAccessibilityImageMapLink(axObjectCache()->getOrCreate(ImageMapLinkRole)); - HTMLMapElement* map = toHTMLMapElement(parent); - areaObject->setHTMLAreaElement(toHTMLAreaElement(curr)); - areaObject->setHTMLMapElement(map); - areaObject->setParent(accessibilityParentForImageMap(map)); - - result.append(areaObject); + auto* parent = current->parentNode(); + if (is<HTMLAreaElement>(*current) && is<HTMLMapElement>(parent)) { + auto& areaObject = downcast<AccessibilityImageMapLink>(*axObjectCache()->getOrCreate(ImageMapLinkRole)); + HTMLMapElement& map = downcast<HTMLMapElement>(*parent); + areaObject.setHTMLAreaElement(downcast<HTMLAreaElement>(current)); + areaObject.setHTMLMapElement(&map); + areaObject.setParent(accessibilityParentForImageMap(&map)); + + result.append(&areaObject); } } } @@ -1772,7 +1832,7 @@ void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& re FrameView* AccessibilityRenderObject::documentFrameView() const { if (!m_renderer) - return 0; + return nullptr; // this is the RenderObject's Document's Frame's FrameView return &m_renderer->view().frameView(); @@ -1781,8 +1841,8 @@ FrameView* AccessibilityRenderObject::documentFrameView() const Widget* AccessibilityRenderObject::widgetForAttachmentView() const { if (!isAttachment()) - return 0; - return toRenderWidget(m_renderer)->widget(); + return nullptr; + return downcast<RenderWidget>(*m_renderer).widget(); } // This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns @@ -1821,7 +1881,7 @@ VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsi // iterate over the lines // FIXME: this is wrong when lineNumber is lineCount+1, because nextLinePosition takes you to the // last offset of the last line - VisiblePosition visiblePos = m_renderer->view().positionForPoint(IntPoint()); + VisiblePosition visiblePos = m_renderer->view().positionForPoint(IntPoint(), nullptr); VisiblePosition savedVisiblePos; while (--lineCount) { savedVisiblePos = visiblePos; @@ -1847,22 +1907,22 @@ VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) co return VisiblePosition(); if (isNativeTextControl()) - return toRenderTextControl(m_renderer)->textFormControlElement().visiblePositionForIndex(index); + return downcast<RenderTextControl>(*m_renderer).textFormControlElement().visiblePositionForIndex(index); - if (!allowsTextRanges() && !m_renderer->isText()) + if (!allowsTextRanges() && !is<RenderText>(*m_renderer)) return VisiblePosition(); Node* node = m_renderer->node(); if (!node) return VisiblePosition(); - return visiblePositionForIndexUsingCharacterIterator(node, index); + return visiblePositionForIndexUsingCharacterIterator(*node, index); } -int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const +int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& position) const { if (isNativeTextControl()) - return toRenderTextControl(m_renderer)->textFormControlElement().indexForVisiblePosition(pos); + return downcast<RenderTextControl>(*m_renderer).textFormControlElement().indexForVisiblePosition(position); if (!isTextControl()) return 0; @@ -1871,7 +1931,7 @@ int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& po if (!node) return 0; - Position indexPosition = pos.deepEquivalent(); + Position indexPosition = position.deepEquivalent(); if (indexPosition.isNull() || highestEditableRoot(indexPosition, HasEditableAXRole) != node) return 0; @@ -1883,13 +1943,13 @@ int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& po bool forSelectionPreservation = false; #endif - return WebCore::indexForVisiblePosition(node, pos, forSelectionPreservation); + return WebCore::indexForVisiblePosition(*node, position, forSelectionPreservation); } Element* AccessibilityRenderObject::rootEditableElementForPosition(const Position& position) const { // Find the root editable or pseudo-editable (i.e. having an editable ARIA role) element. - Element* result = 0; + Element* result = nullptr; Element* rootEditableElement = position.rootEditableElement(); @@ -1911,11 +1971,32 @@ bool AccessibilityRenderObject::nodeIsTextControl(const Node* node) const if (!node) return false; - const AccessibilityObject* axObjectForNode = axObjectCache()->getOrCreate(const_cast<Node*>(node)); - if (!axObjectForNode) - return false; + if (AXObjectCache* cache = axObjectCache()) { + if (AccessibilityObject* axObjectForNode = cache->getOrCreate(const_cast<Node*>(node))) + return axObjectForNode->isTextControl(); + } - return axObjectForNode->isTextControl(); + return false; +} + +IntRect AccessibilityRenderObject::boundsForRects(LayoutRect& rect1, LayoutRect& rect2, RefPtr<Range> dataRange) const +{ + LayoutRect ourRect = rect1; + ourRect.unite(rect2); + + // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead + if (rect1.maxY() != rect2.maxY()) { + LayoutRect boundingBox = dataRange->absoluteBoundingBox(); + String rangeString = plainText(dataRange.get()); + if (rangeString.length() > 1 && !boundingBox.isEmpty()) + ourRect = boundingBox; + } + +#if PLATFORM(MAC) + return m_renderer->view().frameView().contentsToScreen(snappedIntRect(ourRect)); +#else + return snappedIntRect(ourRect); +#endif } IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const @@ -1941,23 +2022,56 @@ IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePo } } - LayoutRect ourrect = rect1; - ourrect.unite(rect2); + RefPtr<Range> dataRange = makeRange(range.start, range.end); + return boundsForRects(rect1, rect2, dataRange); +} + +IntRect AccessibilityRenderObject::boundsForRange(const RefPtr<Range> range) const +{ + if (!range) + return IntRect(); - // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead - if (rect1.maxY() != rect2.maxY()) { - RefPtr<Range> dataRange = makeRange(range.start, range.end); - LayoutRect boundingBox = dataRange->boundingBox(); - String rangeString = plainText(dataRange.get()); - if (rangeString.length() > 1 && !boundingBox.isEmpty()) - ourrect = boundingBox; + AXObjectCache* cache = this->axObjectCache(); + if (!cache) + return IntRect(); + + CharacterOffset start = cache->startOrEndCharacterOffsetForRange(range, true); + CharacterOffset end = cache->startOrEndCharacterOffsetForRange(range, false); + + LayoutRect rect1 = cache->absoluteCaretBoundsForCharacterOffset(start); + LayoutRect rect2 = cache->absoluteCaretBoundsForCharacterOffset(end); + + // readjust for position at the edge of a line. This is to exclude line rect that doesn't need to be accounted in the range bounds. + if (rect2.y() != rect1.y()) { + CharacterOffset endOfFirstLine = cache->endCharacterOffsetOfLine(start); + if (start.isEqual(endOfFirstLine)) { + start = cache->nextCharacterOffset(start, false); + rect1 = cache->absoluteCaretBoundsForCharacterOffset(start); + } + if (end.isEqual(endOfFirstLine)) { + end = cache->previousCharacterOffset(end, false); + rect2 = cache->absoluteCaretBoundsForCharacterOffset(end); + } } -#if PLATFORM(MAC) && !PLATFORM(IOS) - return m_renderer->view().frameView().contentsToScreen(pixelSnappedIntRect(ourrect)); -#else - return pixelSnappedIntRect(ourrect); -#endif + return boundsForRects(rect1, rect2, range); +} + +bool AccessibilityRenderObject::isVisiblePositionRangeInDifferentDocument(const VisiblePositionRange& range) const +{ + if (range.start.isNull() || range.end.isNull()) + return false; + + VisibleSelection newSelection = VisibleSelection(range.start, range.end); + if (Document* newSelectionDocument = newSelection.base().document()) { + if (RefPtr<Frame> newSelectionFrame = newSelectionDocument->frame()) { + Frame* frame = this->frame(); + if (!frame || (newSelectionFrame != frame && newSelectionDocument != frame->document())) + return true; + } + } + + return false; } void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const @@ -1965,12 +2079,24 @@ void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePos if (range.start.isNull() || range.end.isNull()) return; + // In WebKit1, when the top web area sets the selection to be an input element in an iframe, the caret will disappear. + // FrameSelection::setSelectionWithoutUpdatingAppearance is setting the selection on the new frame in this case, and causing this behavior. + if (isWebArea() && parentObject() && parentObject()->isAttachment()) { + if (isVisiblePositionRangeInDifferentDocument(range)) + return; + } + // make selection and tell the document to use it. if it's zero length, then move to that position - if (range.start == range.end) + if (range.start == range.end) { + setTextSelectionIntent(axObjectCache(), AXTextStateChangeTypeSelectionMove); m_renderer->frame().selection().moveTo(range.start, UserTriggered); + clearTextSelectionIntent(axObjectCache()); + } else { + setTextSelectionIntent(axObjectCache(), AXTextStateChangeTypeSelectionExtend); VisibleSelection newSelection = VisibleSelection(range.start, range.end); - m_renderer->frame().selection().setSelection(newSelection); + m_renderer->frame().selection().setSelection(newSelection, FrameSelection::defaultSetSelectionOptions()); + clearTextSelectionIntent(axObjectCache()); } } @@ -1984,17 +2110,17 @@ VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoin if (!renderView) return VisiblePosition(); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) FrameView* frameView = &renderView->frameView(); #endif - Node* innerNode = 0; + Node* innerNode = nullptr; // locate the node containing the point LayoutPoint pointResult; while (1) { LayoutPoint ourpoint; -#if PLATFORM(MAC) && !PLATFORM(IOS) +#if PLATFORM(MAC) ourpoint = frameView->screenToContents(point); #else ourpoint = point; @@ -2014,21 +2140,21 @@ VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoin pointResult = result.localPoint(); // done if hit something other than a widget - if (!renderer->isWidget()) + if (!is<RenderWidget>(*renderer)) break; // descend into widget (FRAME, IFRAME, OBJECT...) - Widget* widget = toRenderWidget(renderer)->widget(); - if (!widget || !widget->isFrameView()) + Widget* widget = downcast<RenderWidget>(*renderer).widget(); + if (!is<FrameView>(widget)) break; - Frame& frame = toFrameView(widget)->frame(); + Frame& frame = downcast<FrameView>(*widget).frame(); renderView = frame.document()->renderView(); -#if PLATFORM(MAC) - frameView = toFrameView(widget); +#if PLATFORM(COCOA) + frameView = downcast<FrameView>(widget); #endif } - return innerNode->renderer()->positionForPoint(pointResult); + return innerNode->renderer()->positionForPoint(pointResult, nullptr); } // NOTE: Consider providing this utility method as AX API @@ -2147,34 +2273,41 @@ IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& rang return IntRect(); } +IntRect AccessibilityRenderObject::doAXBoundsForRangeUsingCharacterOffset(const PlainTextRange& range) const +{ + if (allowsTextRanges()) + return boundsForRange(rangeForPlainTextRange(range)); + return IntRect(); +} + AccessibilityObject* AccessibilityRenderObject::accessibilityImageMapHitTest(HTMLAreaElement* area, const IntPoint& point) const { if (!area) - return 0; + return nullptr; - AccessibilityObject* parent = 0; + AccessibilityObject* parent = nullptr; for (Element* mapParent = area->parentElement(); mapParent; mapParent = mapParent->parentElement()) { - if (isHTMLMapElement(mapParent)) { - parent = accessibilityParentForImageMap(toHTMLMapElement(mapParent)); + if (is<HTMLMapElement>(*mapParent)) { + parent = accessibilityParentForImageMap(downcast<HTMLMapElement>(mapParent)); break; } } if (!parent) - return 0; + return nullptr; for (const auto& child : parent->children()) { if (child->elementRect().contains(point)) return child.get(); } - return 0; + return nullptr; } AccessibilityObject* AccessibilityRenderObject::remoteSVGElementHitTest(const IntPoint& point) const { - AccessibilityObject* remote = remoteSVGRootElement(); + AccessibilityObject* remote = remoteSVGRootElement(Create); if (!remote) - return 0; + return nullptr; IntSize offset = point - roundedIntPoint(boundingBoxRect().location()); return remote->accessibilityHitTest(IntPoint(offset)); @@ -2188,29 +2321,41 @@ AccessibilityObject* AccessibilityRenderObject::elementAccessibilityHitTest(cons return AccessibilityObject::elementAccessibilityHitTest(point); } +static bool shouldUseShadowHostForHitTesting(Node* shadowHost) +{ + // We need to allow automation of mouse events on video tags. + return shadowHost && !shadowHost->hasTagName(videoTag); +} + AccessibilityObject* AccessibilityRenderObject::accessibilityHitTest(const IntPoint& point) const { if (!m_renderer || !m_renderer->hasLayer()) - return 0; + return nullptr; - RenderLayer* layer = toRenderBox(m_renderer)->layer(); + m_renderer->document().updateLayout(); + + RenderLayer* layer = downcast<RenderBox>(*m_renderer).layer(); HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active | HitTestRequest::AccessibilityHitTest); HitTestResult hitTestResult = HitTestResult(point); layer->hitTest(request, hitTestResult); - if (!hitTestResult.innerNode()) - return 0; - Node* node = hitTestResult.innerNode()->deprecatedShadowAncestorNode(); + Node* node = hitTestResult.innerNode(); + if (!node) + return nullptr; + Node* shadowAncestorNode = node->shadowHost(); + if (shouldUseShadowHostForHitTesting(shadowAncestorNode)) + node = shadowAncestorNode; + ASSERT(node); - if (isHTMLAreaElement(node)) - return accessibilityImageMapHitTest(toHTMLAreaElement(node), point); + if (is<HTMLAreaElement>(*node)) + return accessibilityImageMapHitTest(downcast<HTMLAreaElement>(node), point); - if (isHTMLOptionElement(node)) - node = toHTMLOptionElement(node)->ownerSelectElement(); + if (is<HTMLOptionElement>(*node)) + node = downcast<HTMLOptionElement>(*node).ownerSelectElement(); RenderObject* obj = node->renderer(); if (!obj) - return 0; + return nullptr; AccessibilityObject* result = obj->document().axObjectCache()->getOrCreate(obj); result->updateChildrenIfNecessary(); @@ -2271,29 +2416,35 @@ bool AccessibilityRenderObject::shouldFocusActiveDescendant() const AccessibilityObject* AccessibilityRenderObject::activeDescendant() const { if (!m_renderer) - return 0; + return nullptr; - if (m_renderer->node() && !m_renderer->node()->isElementNode()) - return 0; - Element* element = toElement(m_renderer->node()); - - const AtomicString& activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr); + const AtomicString& activeDescendantAttrStr = getAttribute(aria_activedescendantAttr); if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) - return 0; + return nullptr; + + Element* element = this->element(); + if (!element) + return nullptr; Element* target = element->treeScope().getElementById(activeDescendantAttrStr); if (!target) - return 0; + return nullptr; + + if (AXObjectCache* cache = axObjectCache()) { + AccessibilityObject* obj = cache->getOrCreate(target); + if (obj && obj->isAccessibilityRenderObject()) + // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification + return obj; + } - AccessibilityObject* obj = axObjectCache()->getOrCreate(target); - if (obj && obj->isAccessibilityRenderObject()) - // an activedescendant is only useful if it has a renderer, because that's what's needed to post the notification - return obj; - return 0; + return nullptr; } void AccessibilityRenderObject::handleAriaExpandedChanged() { + // This object might be deleted under the call to the parentObject() method. + auto protectedThis = makeRef(*this); + // Find if a parent of this object should handle aria-expanded changes. AccessibilityObject* containerParent = this->parentObject(); while (containerParent) { @@ -2318,23 +2469,29 @@ void AccessibilityRenderObject::handleAriaExpandedChanged() } // Post that the row count changed. + AXObjectCache* cache = axObjectCache(); + if (!cache) + return; + if (containerParent) - axObjectCache()->postNotification(containerParent, document(), AXObjectCache::AXRowCountChanged); + cache->postNotification(containerParent, document(), AXObjectCache::AXRowCountChanged); // Post that the specific row either collapsed or expanded. if (roleValue() == RowRole || roleValue() == TreeItemRole) - axObjectCache()->postNotification(this, document(), isExpanded() ? AXObjectCache::AXRowExpanded : AXObjectCache::AXRowCollapsed); + cache->postNotification(this, document(), isExpanded() ? AXObjectCache::AXRowExpanded : AXObjectCache::AXRowCollapsed); + else + cache->postNotification(this, document(), AXObjectCache::AXExpandedChanged); } void AccessibilityRenderObject::handleActiveDescendantChanged() { - Element* element = toElement(renderer()->node()); + Element* element = downcast<Element>(renderer()->node()); if (!element) return; if (!renderer()->frame().selection().isFocusedAndActive() || renderer()->document().focusedElement() != element) return; - if (toAccessibilityRenderObject(activeDescendant()) && shouldNotifyActiveDescendant()) + if (activeDescendant() && shouldNotifyActiveDescendant()) renderer()->document().axObjectCache()->postNotification(m_renderer, AXObjectCache::AXActiveDescendantChanged); } @@ -2342,15 +2499,15 @@ AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElem { HTMLLabelElement* labelElement = labelElementContainer(); if (!labelElement) - return 0; + return nullptr; HTMLElement* correspondingControl = labelElement->control(); if (!correspondingControl) - return 0; + return nullptr; // Make sure the corresponding control isn't a descendant of this label that's in the middle of being destroyed. if (correspondingControl->renderer() && !correspondingControl->renderer()->parent()) - return 0; + return nullptr; return axObjectCache()->getOrCreate(correspondingControl); } @@ -2358,36 +2515,38 @@ AccessibilityObject* AccessibilityRenderObject::correspondingControlForLabelElem AccessibilityObject* AccessibilityRenderObject::correspondingLabelForControlElement() const { if (!m_renderer) - return 0; + return nullptr; // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should // override the "label" element association. if (hasTextAlternative()) - return 0; + return nullptr; Node* node = m_renderer->node(); - if (node && node->isHTMLElement()) { - HTMLLabelElement* label = labelForElement(toElement(node)); - if (label) + if (is<HTMLElement>(node)) { + if (HTMLLabelElement* label = labelForElement(downcast<HTMLElement>(node))) return axObjectCache()->getOrCreate(label); } - return 0; + return nullptr; } -bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject* renderer) const +bool AccessibilityRenderObject::renderObjectIsObservable(RenderObject& renderer) const { // AX clients will listen for AXValueChange on a text control. - if (renderer->isTextControl()) + if (is<RenderTextControl>(renderer)) return true; // AX clients will listen for AXSelectedChildrenChanged on listboxes. - Node* node = renderer->node(); - if (nodeHasRole(node, "listbox") || (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListBox())) + Node* node = renderer.node(); + if (!node) + return false; + + if (nodeHasRole(node, "listbox") || (is<RenderBoxModelObject>(renderer) && downcast<RenderBoxModelObject>(renderer).isListBox())) return true; // Textboxes should send out notifications. - if (nodeHasRole(node, "textbox")) + if (nodeHasRole(node, "textbox") || (is<Element>(*node) && contentEditableAttributeIsEnabled(downcast<Element>(node)))) return true; return false; @@ -2397,41 +2556,61 @@ AccessibilityObject* AccessibilityRenderObject::observableObject() const { // Find the object going up the parent chain that is used in accessibility to monitor certain notifications. for (RenderObject* renderer = m_renderer; renderer && renderer->node(); renderer = renderer->parent()) { - if (renderObjectIsObservable(renderer)) - return axObjectCache()->getOrCreate(renderer); + if (renderObjectIsObservable(*renderer)) { + if (AXObjectCache* cache = axObjectCache()) + return cache->getOrCreate(renderer); + } } - return 0; + return nullptr; } bool AccessibilityRenderObject::isDescendantOfElementType(const QualifiedName& tagName) const { - for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { - if (parent->node() && parent->node()->hasTagName(tagName)) + for (auto& ancestor : ancestorsOfType<RenderElement>(*m_renderer)) { + if (ancestor.element() && ancestor.element()->hasTagName(tagName)) return true; } return false; } + +String AccessibilityRenderObject::expandedTextValue() const +{ + if (AccessibilityObject* parent = parentObject()) { + if (parent->hasTagName(abbrTag) || parent->hasTagName(acronymTag)) + return parent->getAttribute(titleAttr); + } + + return String(); +} + +bool AccessibilityRenderObject::supportsExpandedTextValue() const +{ + if (roleValue() == StaticTextRole) { + if (AccessibilityObject* parent = parentObject()) + return parent->hasTagName(abbrTag) || parent->hasTagName(acronymTag); + } + + return false; +} AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() { if (!m_renderer) return UnknownRole; - m_ariaRole = determineAriaRoleAttribute(); + // Sometimes we need to ignore the attribute role. Like if a tree is malformed, + // we want to ignore the treeitem's attribute role. + if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole && !shouldIgnoreAttributeRole()) + return m_ariaRole; Node* node = m_renderer->node(); - AccessibilityRole ariaRole = ariaRoleAttribute(); - if (ariaRole != UnknownRole) - return ariaRole; - RenderBoxModelObject* cssBox = renderBoxModelObject(); - if (node && node->isLink()) { - if (cssBox && cssBox->isImage()) - return ImageMapRole; + if (node && node->isLink()) return WebCoreLinkRole; - } + if (node && is<HTMLImageElement>(*node) && downcast<HTMLImageElement>(*node).hasAttributeWithoutSynchronization(usemapAttr)) + return ImageMapRole; if ((cssBox && cssBox->isListItem()) || (node && node->hasTagName(liTag))) return ListItemRole; if (m_renderer->isListMarker()) @@ -2443,7 +2622,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (m_renderer->isText()) return StaticTextRole; if (cssBox && cssBox->isImage()) { - if (node && isHTMLInputElement(node)) + if (is<HTMLInputElement>(node)) return ariaHasPopup() ? PopUpButtonRole : ButtonRole; if (isSVGImage()) return SVGRootRole; @@ -2453,35 +2632,39 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(canvasTag)) return CanvasRole; - if (cssBox && cssBox->isRenderView()) { - // If the iframe is seamless, it should not be announced as a web area to AT clients. - if (document() && document()->shouldDisplaySeamlesslyWithParent()) - return SeamlessWebAreaRole; + if (cssBox && cssBox->isRenderView()) return WebAreaRole; - } - if (cssBox && cssBox->isTextField()) - return TextFieldRole; + if (cssBox && cssBox->isTextField()) { + if (is<HTMLInputElement>(node)) + return downcast<HTMLInputElement>(*node).isSearchField() ? SearchFieldRole : TextFieldRole; + } if (cssBox && cssBox->isTextArea()) return TextAreaRole; - if (node && isHTMLInputElement(node)) { - HTMLInputElement* input = toHTMLInputElement(node); - if (input->isCheckbox()) + if (is<HTMLInputElement>(node)) { + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (input.isCheckbox()) return CheckBoxRole; - if (input->isRadioButton()) + if (input.isRadioButton()) return RadioButtonRole; - if (input->isTextButton()) + if (input.isTextButton()) return buttonRoleType(); - + // On iOS, the date field and time field are popup buttons. On other platforms they are text fields. +#if PLATFORM(IOS) + if (input.isDateField() || input.isTimeField()) + return PopUpButtonRole; +#endif #if ENABLE(INPUT_TYPE_COLOR) - const AtomicString& type = input->getAttribute(typeAttr); - if (equalIgnoringCase(type, "color")) + if (input.isColorControl()) return ColorWellRole; #endif } - + + if (hasContentEditableAttributeSet()) + return TextAreaRole; + if (isFileUploadButton()) return ButtonRole; @@ -2491,23 +2674,11 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (headingLevel()) return HeadingRole; -#if ENABLE(SVG) - if (m_renderer->isSVGImage()) - return ImageRole; if (m_renderer->isSVGRoot()) return SVGRootRole; - if (node && node->hasTagName(SVGNames::gTag)) - return GroupRole; -#endif - -#if ENABLE(MATHML) - if (node && node->hasTagName(MathMLNames::mathTag)) - return DocumentMathRole; -#endif - // It's not clear which role a platform should choose for a math element. - // Declaring a math element role should give flexibility to platforms to choose. - if (isMathElement()) - return MathElementRole; + + if (isStyleFormatGroup()) + return is<RenderInline>(*m_renderer) ? InlineRole : GroupRole; if (node && node->hasTagName(ddTag)) return DescriptionListDetailRole; @@ -2518,19 +2689,27 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(dlTag)) return DescriptionListRole; - if (node && (node->hasTagName(rpTag) || node->hasTagName(rtTag))) - return AnnotationRole; - + // Check for Ruby elements + if (m_renderer->isRubyText()) + return RubyTextRole; + if (m_renderer->isRubyBase()) + return RubyBaseRole; + if (m_renderer->isRubyRun()) + return RubyRunRole; + if (m_renderer->isRubyBlock()) + return RubyBlockRole; + if (m_renderer->isRubyInline()) + return RubyInlineRole; + + // This return value is what will be used if AccessibilityTableCell determines + // the cell should not be treated as a cell (e.g. because it is a layout table. + // In ATK, there is a distinction between generic text block elements and other + // generic containers; AX API does not make this distinction. + if (is<RenderTableCell>(m_renderer)) #if PLATFORM(GTK) - // Gtk ATs expect all tables, data and layout, to be exposed as tables. - if (node && (node->hasTagName(tdTag) || node->hasTagName(thTag))) - return CellRole; - - if (node && node->hasTagName(trTag)) - return RowRole; - - if (node && isHTMLTableElement(node)) - return TableRole; + return DivRole; +#else + return GroupRole; #endif // Table sections should be ignored. @@ -2543,7 +2722,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(pTag)) return ParagraphRole; - if (node && isHTMLLabelElement(node)) + if (is<HTMLLabelElement>(node)) return LabelRole; if (node && node->hasTagName(dfnTag)) @@ -2552,7 +2731,7 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(divTag)) return DivRole; - if (node && isHTMLFormElement(node)) + if (is<HTMLFormElement>(node)) return FormRole; if (node && node->hasTagName(articleTag)) @@ -2567,16 +2746,41 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() if (node && node->hasTagName(asideTag)) return LandmarkComplementaryRole; + // The default role attribute value for the section element, region, became a landmark in ARIA 1.1. + // The HTML AAM spec says it is "strongly recommended" that ATs only convey and provide navigation + // for section elements which have names. if (node && node->hasTagName(sectionTag)) - return DocumentRegionRole; + return hasAttribute(aria_labelAttr) || hasAttribute(aria_labelledbyAttr) ? LandmarkRegionRole : GroupRole; if (node && node->hasTagName(addressTag)) return LandmarkContentInfoRole; + if (node && node->hasTagName(blockquoteTag)) + return BlockquoteRole; + + if (node && node->hasTagName(captionTag)) + return CaptionRole; + + if (node && node->hasTagName(markTag)) + return MarkRole; + + if (node && node->hasTagName(preTag)) + return PreRole; + + if (is<HTMLDetailsElement>(node)) + return DetailsRole; + if (is<HTMLSummaryElement>(node)) + return SummaryRole; + + // http://rawgit.com/w3c/aria/master/html-aam/html-aam.html + // Output elements should be mapped to status role. + if (isOutput()) + return ApplicationStatusRole; + #if ENABLE(VIDEO) - if (node && isHTMLVideoElement(node)) + if (is<HTMLVideoElement>(node)) return VideoRole; - if (node && isHTMLAudioElement(node)) + if (is<HTMLAudioElement>(node)) return AudioRole; #endif @@ -2590,25 +2794,55 @@ AccessibilityRole AccessibilityRenderObject::determineAccessibilityRole() return LandmarkBannerRole; if (node && node->hasTagName(footerTag) && !isDescendantOfElementType(articleTag) && !isDescendantOfElementType(sectionTag)) return FooterRole; - - if (m_renderer->isRenderBlockFlow()) - return GroupRole; + + // menu tags with toolbar type should have Toolbar role. + if (node && node->hasTagName(menuTag) && equalLettersIgnoringASCIICase(getAttribute(typeAttr), "toolbar")) + return ToolbarRole; // If the element does not have role, but it has ARIA attributes, or accepts tab focus, accessibility should fallback to exposing it as a group. if (supportsARIAAttributes() || canSetFocusAttribute()) return GroupRole; + + if (m_renderer->isRenderBlockFlow()) { +#if PLATFORM(GTK) + // For ATK, GroupRole maps to ATK_ROLE_PANEL. Panels are most commonly found (and hence + // expected) in UI elements; not text blocks. + return m_renderer->isAnonymousBlock() ? DivRole : GroupRole; +#else + return GroupRole; +#endif + } + // InlineRole is the final fallback before assigning UnknownRole to an object. It makes it + // possible to distinguish truly unknown objects from non-focusable inline text elements + // which have an event handler or attribute suggesting possible inclusion by the platform. + if (is<RenderInline>(*m_renderer) + && (hasAttributesRequiredForInclusion() + || (node && node->hasEventListeners()) + || (supportsDatetimeAttribute() && !getAttribute(datetimeAttr).isEmpty()))) + return InlineRole; + return UnknownRole; } AccessibilityOrientation AccessibilityRenderObject::orientation() const { const AtomicString& ariaOrientation = getAttribute(aria_orientationAttr); - if (equalIgnoringCase(ariaOrientation, "horizontal")) + if (equalLettersIgnoringASCIICase(ariaOrientation, "horizontal")) return AccessibilityOrientationHorizontal; - if (equalIgnoringCase(ariaOrientation, "vertical")) + if (equalLettersIgnoringASCIICase(ariaOrientation, "vertical")) + return AccessibilityOrientationVertical; + if (equalLettersIgnoringASCIICase(ariaOrientation, "undefined")) + return AccessibilityOrientationUndefined; + + // ARIA 1.1 Implicit defaults are defined on some roles. + // http://www.w3.org/TR/wai-aria-1.1/#aria-orientation + if (isScrollbar() || isComboBox() || isListBox() || isMenu() || isTree()) return AccessibilityOrientationVertical; + if (isMenuBar() || isSplitter() || isTabList() || isToolbar()) + return AccessibilityOrientationHorizontal; + return AccessibilityObject::orientation(); } @@ -2621,18 +2855,25 @@ bool AccessibilityRenderObject::inheritsPresentationalRole() const // ARIA spec says that when a parent object is presentational, and it has required child elements, // those child elements are also presentational. For example, <li> becomes presentational from <ul>. // http://www.w3.org/WAI/PF/aria/complete#presentation - DEFINE_STATIC_LOCAL(HashSet<QualifiedName>, listItemParents, ()); + static NeverDestroyed<HashSet<QualifiedName>> listItemParents; + static NeverDestroyed<HashSet<QualifiedName>> tableCellParents; - HashSet<QualifiedName>* possibleParentTagNames = 0; + HashSet<QualifiedName>* possibleParentTagNames = nullptr; switch (roleValue()) { case ListItemRole: case ListMarkerRole: - if (listItemParents.isEmpty()) { - listItemParents.add(ulTag); - listItemParents.add(olTag); - listItemParents.add(dlTag); + if (listItemParents.get().isEmpty()) { + listItemParents.get().add(ulTag); + listItemParents.get().add(olTag); + listItemParents.get().add(dlTag); } - possibleParentTagNames = &listItemParents; + possibleParentTagNames = &listItemParents.get(); + break; + case GridCellRole: + case CellRole: + if (tableCellParents.get().isEmpty()) + tableCellParents.get().add(tableTag); + possibleParentTagNames = &tableCellParents.get(); break; default: break; @@ -2643,16 +2884,16 @@ bool AccessibilityRenderObject::inheritsPresentationalRole() const return false; for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { - if (!parent->isAccessibilityRenderObject()) + if (!is<AccessibilityRenderObject>(*parent)) continue; - Node* elementNode = toAccessibilityRenderObject(parent)->node(); - if (!elementNode || !elementNode->isElementNode()) + Node* node = downcast<AccessibilityRenderObject>(*parent).node(); + if (!is<Element>(node)) continue; // If native tag of the parent element matches an acceptable name, then return // based on its presentational status. - if (possibleParentTagNames->contains(toElement(elementNode)->tagQName())) + if (possibleParentTagNames->contains(downcast<Element>(node)->tagQName())) return parent->roleValue() == PresentationalRole; } @@ -2686,37 +2927,12 @@ bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const bool AccessibilityRenderObject::canSetExpandedAttribute() const { + if (roleValue() == DetailsRole) + return true; + // An object can be expanded if it aria-expanded is true or false. const AtomicString& ariaExpanded = getAttribute(aria_expandedAttr); - return equalIgnoringCase(ariaExpanded, "true") || equalIgnoringCase(ariaExpanded, "false"); -} - -bool AccessibilityRenderObject::canSetValueAttribute() const -{ - - // In the event of a (Boolean)@readonly and (True/False/Undefined)@aria-readonly - // value mismatch, the host language native attribute value wins. - if (isNativeTextControl()) - return !isReadOnly(); - - if (isMeter()) - return false; - - if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "true")) - return false; - - if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "false")) - return true; - - if (isProgressIndicator() || isSlider()) - return true; - - if (isTextControl() && !isNativeTextControl()) - return true; - - // Any node could be contenteditable, so isReadOnly should be relied upon - // for this information for all elements. - return !isReadOnly(); + return equalLettersIgnoringASCIICase(ariaExpanded, "true") || equalLettersIgnoringASCIICase(ariaExpanded, "false"); } bool AccessibilityRenderObject::canSetTextRangeAttributes() const @@ -2729,15 +2945,18 @@ void AccessibilityRenderObject::textChanged() // If this element supports ARIA live regions, or is part of a region with an ARIA editable role, // then notify the AT of changes. AXObjectCache* cache = axObjectCache(); + if (!cache) + return; + for (RenderObject* renderParent = m_renderer; renderParent; renderParent = renderParent->parent()) { AccessibilityObject* parent = cache->get(renderParent); if (!parent) continue; if (parent->supportsARIALiveRegion()) - cache->postNotification(renderParent, AXObjectCache::AXLiveRegionChanged); + cache->postLiveRegionChangeNotification(parent); - if (parent->isARIATextControl() && !parent->isNativeTextControl() && !parent->node()->hasEditableStyle()) + if (parent->isNonNativeTextControl()) cache->postNotification(renderParent, AXObjectCache::AXValueChanged); } } @@ -2751,10 +2970,10 @@ void AccessibilityRenderObject::clearChildren() void AccessibilityRenderObject::addImageMapChildren() { RenderBoxModelObject* cssBox = renderBoxModelObject(); - if (!cssBox || !cssBox->isRenderImage()) + if (!is<RenderImage>(cssBox)) return; - HTMLMapElement* map = toRenderImage(cssBox)->imageMap(); + HTMLMapElement* map = downcast<RenderImage>(*cssBox).imageMap(); if (!map) return; @@ -2762,14 +2981,14 @@ void AccessibilityRenderObject::addImageMapChildren() // add an <area> element for this child if it has a link if (!area.isLink()) continue; - AccessibilityImageMapLink* areaObject = toAccessibilityImageMapLink(axObjectCache()->getOrCreate(ImageMapLinkRole)); - areaObject->setHTMLAreaElement(&area); - areaObject->setHTMLMapElement(map); - areaObject->setParent(this); - if (!areaObject->accessibilityIsIgnored()) - m_children.append(areaObject); + auto& areaObject = downcast<AccessibilityImageMapLink>(*axObjectCache()->getOrCreate(ImageMapLinkRole)); + areaObject.setHTMLAreaElement(&area); + areaObject.setHTMLMapElement(map); + areaObject.setParent(this); + if (!areaObject.accessibilityIsIgnored()) + m_children.append(&areaObject); else - axObjectCache()->remove(areaObject->axObjectID()); + axObjectCache()->remove(areaObject.axObjectID()); } } @@ -2784,56 +3003,59 @@ void AccessibilityRenderObject::updateChildrenIfNecessary() void AccessibilityRenderObject::addTextFieldChildren() { Node* node = this->node(); - if (!node || !isHTMLInputElement(node)) + if (!is<HTMLInputElement>(node)) return; - HTMLInputElement* input = toHTMLInputElement(node); - HTMLElement* spinButtonElement = input->innerSpinButtonElement(); - if (!spinButtonElement || !spinButtonElement->isSpinButtonElement()) + HTMLInputElement& input = downcast<HTMLInputElement>(*node); + if (HTMLElement* autoFillElement = input.autoFillButtonElement()) { + if (AccessibilityObject* axAutoFill = axObjectCache()->getOrCreate(autoFillElement)) + m_children.append(axAutoFill); + } + + HTMLElement* spinButtonElement = input.innerSpinButtonElement(); + if (!is<SpinButtonElement>(spinButtonElement)) return; - AccessibilitySpinButton* axSpinButton = toAccessibilitySpinButton(axObjectCache()->getOrCreate(SpinButtonRole)); - axSpinButton->setSpinButtonElement(static_cast<SpinButtonElement*>(spinButtonElement)); - axSpinButton->setParent(this); - m_children.append(axSpinButton); + auto& axSpinButton = downcast<AccessibilitySpinButton>(*axObjectCache()->getOrCreate(SpinButtonRole)); + axSpinButton.setSpinButtonElement(downcast<SpinButtonElement>(spinButtonElement)); + axSpinButton.setParent(this); + m_children.append(&axSpinButton); } bool AccessibilityRenderObject::isSVGImage() const { - return remoteSVGRootElement(); + return remoteSVGRootElement(Create); } void AccessibilityRenderObject::detachRemoteSVGRoot() { - if (AccessibilitySVGRoot* root = remoteSVGRootElement()) - root->setParent(0); + if (AccessibilitySVGRoot* root = remoteSVGRootElement(Retrieve)) + root->setParent(nullptr); } -AccessibilitySVGRoot* AccessibilityRenderObject::remoteSVGRootElement() const +AccessibilitySVGRoot* AccessibilityRenderObject::remoteSVGRootElement(CreationChoice createIfNecessary) const { -#if ENABLE(SVG) - if (!m_renderer || !m_renderer->isRenderImage()) + if (!is<RenderImage>(m_renderer)) return nullptr; - CachedImage* cachedImage = toRenderImage(m_renderer)->cachedImage(); + CachedImage* cachedImage = downcast<RenderImage>(*m_renderer).cachedImage(); if (!cachedImage) return nullptr; Image* image = cachedImage->image(); - if (!image || !image->isSVGImage()) + if (!is<SVGImage>(image)) return nullptr; - SVGImage* svgImage = static_cast<SVGImage*>(image); - FrameView* frameView = svgImage->frameView(); + FrameView* frameView = downcast<SVGImage>(*image).frameView(); if (!frameView) return nullptr; Frame& frame = frameView->frame(); - Document* doc = frame.document(); - if (!doc || !doc->isSVGDocument()) + Document* document = frame.document(); + if (!is<SVGDocument>(document)) return nullptr; - SVGSVGElement* rootElement = toSVGDocument(doc)->rootElement(); + SVGSVGElement* rootElement = SVGDocument::rootElement(*document); if (!rootElement) return nullptr; RenderObject* rendererRoot = rootElement->renderer(); @@ -2843,23 +3065,20 @@ AccessibilitySVGRoot* AccessibilityRenderObject::remoteSVGRootElement() const AXObjectCache* cache = frame.document()->axObjectCache(); if (!cache) return nullptr; - AccessibilityObject* rootSVGObject = cache->getOrCreate(rendererRoot); + AccessibilityObject* rootSVGObject = createIfNecessary == Create ? cache->getOrCreate(rendererRoot) : cache->get(rendererRoot); // In order to connect the AX hierarchy from the SVG root element from the loaded resource // the parent must be set, because there's no other way to get back to who created the image. - ASSERT(rootSVGObject && rootSVGObject->isAccessibilitySVGRoot()); - if (!rootSVGObject->isAccessibilitySVGRoot()) + ASSERT(!createIfNecessary || rootSVGObject); + if (!is<AccessibilitySVGRoot>(rootSVGObject)) return nullptr; - return toAccessibilitySVGRoot(rootSVGObject); -#else - return nullptr; -#endif + return downcast<AccessibilitySVGRoot>(rootSVGObject); } void AccessibilityRenderObject::addRemoteSVGChildren() { - AccessibilitySVGRoot* root = remoteSVGRootElement(); + AccessibilitySVGRoot* root = remoteSVGRootElement(Create); if (!root) return; @@ -2901,7 +3120,7 @@ void AccessibilityRenderObject::addAttachmentChildren() m_children.append(axWidget); } -#if PLATFORM(MAC) +#if PLATFORM(COCOA) void AccessibilityRenderObject::updateAttachmentViewParents() { // Only the unignored parent should set the attachment parent, because that's what is reflected in the AX @@ -2949,7 +3168,7 @@ void AccessibilityRenderObject::addHiddenChildren() if (children.size()) childObject = children.last().get(); else - childObject = 0; + childObject = nullptr; } if (childObject) @@ -2969,6 +3188,24 @@ void AccessibilityRenderObject::addHiddenChildren() } } +void AccessibilityRenderObject::updateRoleAfterChildrenCreation() +{ + // If a menu does not have valid menuitem children, it should not be exposed as a menu. + if (roleValue() == MenuRole) { + // Elements marked as menus must have at least one menu item child. + size_t menuItemCount = 0; + for (const auto& child : children()) { + if (child->isMenuItem()) { + menuItemCount++; + break; + } + } + + if (!menuItemCount) + m_role = GroupRole; + } +} + void AccessibilityRenderObject::addChildren() { // If the need to add more children in addition to existing children arises, @@ -2990,9 +3227,11 @@ void AccessibilityRenderObject::addChildren() addCanvasChildren(); addRemoteSVGChildren(); -#if PLATFORM(MAC) +#if PLATFORM(COCOA) updateAttachmentViewParents(); #endif + + updateRoleAfterChildrenCreation(); } bool AccessibilityRenderObject::canHaveChildren() const @@ -3003,36 +3242,19 @@ bool AccessibilityRenderObject::canHaveChildren() const return AccessibilityNodeObject::canHaveChildren(); } -const AtomicString& AccessibilityRenderObject::ariaLiveRegionStatus() const +const String AccessibilityRenderObject::ariaLiveRegionStatus() const { - DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive, ("assertive", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite", AtomicString::ConstructFromLiteral)); - DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off", AtomicString::ConstructFromLiteral)); - const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr); // These roles have implicit live region status. - if (liveRegionStatus.isEmpty()) { - switch (roleValue()) { - case ApplicationAlertDialogRole: - case ApplicationAlertRole: - return liveRegionStatusAssertive; - case ApplicationLogRole: - case ApplicationStatusRole: - return liveRegionStatusPolite; - case ApplicationTimerRole: - case ApplicationMarqueeRole: - return liveRegionStatusOff; - default: - break; - } - } + if (liveRegionStatus.isEmpty()) + return defaultLiveRegionStatusForRole(roleValue()); return liveRegionStatus; } const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const { - DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant, ("additions text", AtomicString::ConstructFromLiteral)); + static NeverDestroyed<const AtomicString> defaultLiveRegionRelevant("additions text", AtomicString::ConstructFromLiteral); const AtomicString& relevant = getAttribute(aria_relevantAttr); // Default aria-relevant = "additions text". @@ -3044,23 +3266,49 @@ const AtomicString& AccessibilityRenderObject::ariaLiveRegionRelevant() const bool AccessibilityRenderObject::ariaLiveRegionAtomic() const { - return elementAttributeValue(aria_atomicAttr); + const AtomicString& atomic = getAttribute(aria_atomicAttr); + if (equalLettersIgnoringASCIICase(atomic, "true")) + return true; + if (equalLettersIgnoringASCIICase(atomic, "false")) + return false; + + // WAI-ARIA "alert" and "status" roles have an implicit aria-atomic value of true. + switch (roleValue()) { + case ApplicationAlertRole: + case ApplicationStatusRole: + return true; + default: + return false; + } } -bool AccessibilityRenderObject::ariaLiveRegionBusy() const +bool AccessibilityRenderObject::isBusy() const { return elementAttributeValue(aria_busyAttr); } + +bool AccessibilityRenderObject::canHaveSelectedChildren() const +{ + switch (roleValue()) { + // These roles are containers whose children support aria-selected: + case GridRole: + case ListBoxRole: + case TabListRole: + case TreeRole: + case TreeGridRole: + // These roles are containers whose children are treated as selected by assistive + // technologies. We can get the "selected" item via aria-activedescendant or the + // focused element. + case MenuRole: + case MenuBarRole: + return true; + default: + return false; + } +} void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& result) { - // Get all the rows. - AccessibilityChildrenVector allRows; - if (isTree()) - ariaTreeRows(allRows); - else if (isAccessibilityTable() && toAccessibilityTable(this)->supportsSelectedRows()) - allRows = toAccessibilityTable(this)->rows(); - // Determine which rows are selected. bool isMulti = isMultiSelectable(); @@ -3072,12 +3320,24 @@ void AccessibilityRenderObject::ariaSelectedRows(AccessibilityChildrenVector& re return; } - for (const auto& row : allRows) { - if (row->isSelected()) { - result.append(row); - if (!isMulti) - break; + // Get all the rows. + auto rowsIteration = [&](auto& rows) { + for (auto& row : rows) { + if (row->isSelected()) { + result.append(row); + if (!isMulti) + break; + } } + }; + if (isTree()) { + AccessibilityChildrenVector allRows; + ariaTreeRows(allRows); + rowsIteration(allRows); + } else if (is<AccessibilityTable>(*this)) { + auto& thisTable = downcast<AccessibilityTable>(*this); + if (thisTable.isExposableThroughAccessibility() && thisTable.supportsSelectedRows()) + rowsIteration(thisTable.rows()); } } @@ -3099,12 +3359,36 @@ void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& re { ASSERT(result.isEmpty()); - // only listboxes should be asked for their selected children. - AccessibilityRole role = roleValue(); - if (role == ListBoxRole) // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes + if (!canHaveSelectedChildren()) + return; + + switch (roleValue()) { + case ListBoxRole: + // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes ariaListboxSelectedChildren(result); - else if (role == TreeRole || role == TreeGridRole || role == TableRole) + return; + case GridRole: + case TreeRole: ariaSelectedRows(result); + return; + case TabListRole: + if (AccessibilityObject* selectedTab = selectedTabItem()) + result.append(selectedTab); + return; + case MenuRole: + case MenuBarRole: + if (AccessibilityObject* descendant = activeDescendant()) { + result.append(descendant); + return; + } + if (AccessibilityObject* focusedElement = focusedUIElement()) { + result.append(focusedElement); + return; + } + return; + default: + ASSERT_NOT_REACHED(); + } } void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result) @@ -3144,12 +3428,12 @@ const String& AccessibilityRenderObject::actionVerb() const { #if !PLATFORM(IOS) // FIXME: Need to add verbs for select elements. - DEFINE_STATIC_LOCAL(const String, buttonAction, (AXButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, textFieldAction, (AXTextFieldActionVerb())); - DEFINE_STATIC_LOCAL(const String, radioButtonAction, (AXRadioButtonActionVerb())); - DEFINE_STATIC_LOCAL(const String, checkedCheckBoxAction, (AXCheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, uncheckedCheckBoxAction, (AXUncheckedCheckBoxActionVerb())); - DEFINE_STATIC_LOCAL(const String, linkAction, (AXLinkActionVerb())); + static NeverDestroyed<const String> buttonAction(AXButtonActionVerb()); + static NeverDestroyed<const String> textFieldAction(AXTextFieldActionVerb()); + static NeverDestroyed<const String> radioButtonAction(AXRadioButtonActionVerb()); + static NeverDestroyed<const String> checkedCheckBoxAction(AXUncheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> uncheckedCheckBoxAction(AXUncheckedCheckBoxActionVerb()); + static NeverDestroyed<const String> linkAction(AXLinkActionVerb()); switch (roleValue()) { case ButtonRole: @@ -3179,15 +3463,15 @@ void AccessibilityRenderObject::setAccessibleName(const AtomicString& name) if (!m_renderer) return; - Node* domNode = 0; + Node* node = nullptr; // For web areas, set the aria-label on the HTML element. if (isWebArea()) - domNode = m_renderer->document().documentElement(); + node = m_renderer->document().documentElement(); else - domNode = m_renderer->node(); + node = m_renderer->node(); - if (domNode && domNode->isElementNode()) - toElement(domNode)->setAttribute(aria_labelAttr, name); + if (is<Element>(node)) + downcast<Element>(*node).setAttribute(aria_labelAttr, name); } static bool isLinkable(const AccessibilityRenderObject& object) @@ -3204,8 +3488,8 @@ String AccessibilityRenderObject::stringValueForMSAA() const { if (isLinkable(*this)) { Element* anchor = anchorElement(); - if (anchor && isHTMLAnchorElement(anchor)) - return toHTMLAnchorElement(anchor)->href(); + if (is<HTMLAnchorElement>(anchor)) + return downcast<HTMLAnchorElement>(*anchor).href(); } return stringValue(); @@ -3217,10 +3501,10 @@ bool AccessibilityRenderObject::isLinked() const return false; Element* anchor = anchorElement(); - if (!anchor || !isHTMLAnchorElement(anchor)) + if (!is<HTMLAnchorElement>(anchor)) return false; - return !toHTMLAnchorElement(anchor)->href().isEmpty(); + return !downcast<HTMLAnchorElement>(*anchor).href().isEmpty(); } bool AccessibilityRenderObject::hasBoldFont() const @@ -3293,12 +3577,16 @@ String AccessibilityRenderObject::nameForMSAA() const static bool shouldReturnTagNameAsRoleForMSAA(const Element& element) { - // See "document structure", - // https://wiki.mozilla.org/Accessibility/AT-Windows-API - // FIXME: Add the other tag names that should be returned as the role. - return element.hasTagName(h1Tag) || element.hasTagName(h2Tag) + return element.hasTagName(abbrTag) || element.hasTagName(acronymTag) + || element.hasTagName(blockquoteTag) || element.hasTagName(ddTag) + || element.hasTagName(dlTag) || element.hasTagName(dtTag) + || element.hasTagName(formTag) || element.hasTagName(frameTag) + || element.hasTagName(h1Tag) || element.hasTagName(h2Tag) || element.hasTagName(h3Tag) || element.hasTagName(h4Tag) - || element.hasTagName(h5Tag) || element.hasTagName(h6Tag); + || element.hasTagName(h5Tag) || element.hasTagName(h6Tag) + || element.hasTagName(iframeTag) || element.hasTagName(qTag) + || element.hasTagName(tbodyTag) || element.hasTagName(tfootTag) + || element.hasTagName(theadTag); } String AccessibilityRenderObject::stringRoleForMSAA() const @@ -3307,14 +3595,14 @@ String AccessibilityRenderObject::stringRoleForMSAA() const return String(); Node* node = m_renderer->node(); - if (!node || !node->isElementNode()) + if (!is<Element>(node)) return String(); - Element* element = toElement(node); - if (!shouldReturnTagNameAsRoleForMSAA(*element)) + Element& element = downcast<Element>(*node); + if (!shouldReturnTagNameAsRoleForMSAA(element)) return String(); - return element->tagName(); + return element.tagName(); } String AccessibilityRenderObject::positionalDescriptionForMSAA() const @@ -3352,10 +3640,10 @@ static AccessibilityRole msaaRoleForRenderer(const RenderObject* renderer) if (!renderer) return UnknownRole; - if (renderer->isText()) + if (is<RenderText>(*renderer)) return EditableTextRole; - if (renderer->isBoxModelObject() && toRenderBoxModelObject(renderer)->isListItem()) + if (is<RenderListItem>(*renderer)) return ListItemRole; return UnknownRole; @@ -3380,397 +3668,52 @@ String AccessibilityRenderObject::passwordFieldValue() const // Look for the RenderText object in the RenderObject tree for this input field. RenderObject* renderer = node()->renderer(); - while (renderer && !renderer->isText()) - renderer = toRenderElement(renderer)->firstChild(); + while (renderer && !is<RenderText>(renderer)) + renderer = downcast<RenderElement>(*renderer).firstChild(); - if (!renderer || !renderer->isText()) + if (!is<RenderText>(renderer)) return String(); // Return the text that is actually being rendered in the input field. - return toRenderText(renderer)->textWithoutConvertingBackslashToYenSymbol(); + return downcast<RenderText>(*renderer).textWithoutConvertingBackslashToYenSymbol(); } ScrollableArea* AccessibilityRenderObject::getScrollableAreaIfScrollable() const { // If the parent is a scroll view, then this object isn't really scrollable, the parent ScrollView should handle the scrolling. if (parentObject() && parentObject()->isAccessibilityScrollView()) - return 0; + return nullptr; - if (!m_renderer || !m_renderer->isBox()) - return 0; + if (!is<RenderBox>(m_renderer)) + return nullptr; - RenderBox* box = toRenderBox(m_renderer); - if (!box->canBeScrolledAndHasScrollableArea()) - return 0; + auto& box = downcast<RenderBox>(*m_renderer); + if (!box.canBeScrolledAndHasScrollableArea()) + return nullptr; - return box->layer(); + return box.layer(); } void AccessibilityRenderObject::scrollTo(const IntPoint& point) const { - if (!m_renderer || !m_renderer->isBox()) + if (!is<RenderBox>(m_renderer)) return; - RenderBox* box = toRenderBox(m_renderer); - if (!box->canBeScrolledAndHasScrollableArea()) + auto& box = downcast<RenderBox>(*m_renderer); + if (!box.canBeScrolledAndHasScrollableArea()) return; - RenderLayer* layer = box->layer(); - layer->scrollToOffset(toIntSize(point), RenderLayer::ScrollOffsetClamped); + // FIXME: is point a ScrollOffset or ScrollPosition? Test in RTL overflow. + box.layer()->scrollToOffset(point, RenderLayer::ScrollOffsetClamped); } #if ENABLE(MATHML) -bool AccessibilityRenderObject::isMathElement() const -{ - Node* node = this->node(); - if (!m_renderer || !node) - return false; - - return node->isMathMLElement(); -} - -bool AccessibilityRenderObject::isMathFraction() const -{ - return m_renderer && m_renderer->isRenderMathMLFraction(); -} - -bool AccessibilityRenderObject::isMathFenced() const -{ - return m_renderer && m_renderer->isRenderMathMLFenced(); -} - -bool AccessibilityRenderObject::isMathSubscriptSuperscript() const -{ - return m_renderer && m_renderer->isRenderMathMLScripts() && !isMathMultiscript(); -} - -bool AccessibilityRenderObject::isMathRow() const -{ - return m_renderer && m_renderer->isRenderMathMLRow(); -} - -bool AccessibilityRenderObject::isMathUnderOver() const -{ - return m_renderer && m_renderer->isRenderMathMLUnderOver(); -} - -bool AccessibilityRenderObject::isMathSquareRoot() const -{ - return m_renderer && m_renderer->isRenderMathMLSquareRoot(); -} - -bool AccessibilityRenderObject::isMathRoot() const -{ - return m_renderer && m_renderer->isRenderMathMLRoot(); -} - -bool AccessibilityRenderObject::isMathOperator() const -{ - if (!m_renderer || !m_renderer->isRenderMathMLOperator()) - return false; - - // Ensure that this is actually a render MathML operator because - // MathML will create MathMLBlocks and use the original node as the node - // of this new block that is not tied to the DOM. - return isMathElement() && node()->hasTagName(MathMLNames::moTag); -} - -bool AccessibilityRenderObject::isMathFenceOperator() const -{ - if (!m_renderer || !m_renderer->isRenderMathMLOperator()) - return false; - - return toRenderMathMLOperator(*m_renderer).operatorType() == RenderMathMLOperator::Fence; -} - -bool AccessibilityRenderObject::isMathSeparatorOperator() const -{ - if (!m_renderer || !m_renderer->isRenderMathMLOperator()) - return false; - - return toRenderMathMLOperator(*m_renderer).operatorType() == RenderMathMLOperator::Separator; -} - -bool AccessibilityRenderObject::isMathText() const -{ - return node() && node()->hasTagName(MathMLNames::mtextTag); -} - -bool AccessibilityRenderObject::isMathNumber() const -{ - return node() && node()->hasTagName(MathMLNames::mnTag); -} - -bool AccessibilityRenderObject::isMathIdentifier() const -{ - return node() && node()->hasTagName(MathMLNames::miTag); -} - -bool AccessibilityRenderObject::isMathMultiscript() const -{ - return node() && node()->hasTagName(MathMLNames::mmultiscriptsTag); -} - -bool AccessibilityRenderObject::isMathTable() const -{ - return node() && node()->hasTagName(MathMLNames::mtableTag); -} - -bool AccessibilityRenderObject::isMathTableRow() const -{ - return node() && node()->hasTagName(MathMLNames::mtrTag); -} - -bool AccessibilityRenderObject::isMathTableCell() const -{ - return node() && node()->hasTagName(MathMLNames::mtdTag); -} - bool AccessibilityRenderObject::isIgnoredElementWithinMathTree() const { - if (!m_renderer) - return true; - - // Ignore items that were created for layout purposes only. - if (m_renderer->isRenderMathMLBlock() && toRenderMathMLBlock(m_renderer)->ignoreInAccessibilityTree()) - return true; - - // Ignore anonymous renderers inside math blocks. - if (m_renderer->isAnonymous()) { - for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { - if (parent->isMathElement()) - return true; - } - } - - // Only math elements that we explicitly recognize should be included - // We don't want things like <mstyle> to appear in the tree. - if (isMathElement()) { - if (isMathFraction() || isMathFenced() || isMathSubscriptSuperscript() || isMathRow() - || isMathUnderOver() || isMathRoot() || isMathText() || isMathNumber() - || isMathOperator() || isMathFenceOperator() || isMathSeparatorOperator() - || isMathIdentifier() || isMathTable() || isMathTableRow() || isMathTableCell() || isMathMultiscript()) - return false; - return true; - } - - return false; -} - -AccessibilityObject* AccessibilityRenderObject::mathRadicandObject() -{ - if (!isMathRoot()) - return 0; - - const auto& children = this->children(); - if (children.size() < 1) - return 0; - - // The radicand is the value being rooted and must be listed first. - return children[0].get(); + // We ignore anonymous boxes inserted into RenderMathMLBlocks to honor CSS rules. + // See https://www.w3.org/TR/css3-box/#block-level0 + return m_renderer && m_renderer->isAnonymous() && m_renderer->parent() && is<RenderMathMLBlock>(m_renderer->parent()); } - -AccessibilityObject* AccessibilityRenderObject::mathRootIndexObject() -{ - if (!isMathRoot()) - return 0; - - const auto& children = this->children(); - if (children.size() != 2) - return 0; - - // The index in a root is the value which determines if it's a square, cube, etc, root - // and must be listed second. - return children[1].get(); -} - -AccessibilityObject* AccessibilityRenderObject::mathNumeratorObject() -{ - if (!isMathFraction()) - return 0; - - const auto& children = this->children(); - if (children.size() != 2) - return 0; - - return children[0].get(); -} - -AccessibilityObject* AccessibilityRenderObject::mathDenominatorObject() -{ - if (!isMathFraction()) - return 0; - - const auto& children = this->children(); - if (children.size() != 2) - return 0; - - return children[1].get(); -} - -AccessibilityObject* AccessibilityRenderObject::mathUnderObject() -{ - if (!isMathUnderOver() || !node()) - return 0; - - const auto& children = this->children(); - if (children.size() < 2) - return 0; - - if (node()->hasTagName(MathMLNames::munderTag) || node()->hasTagName(MathMLNames::munderoverTag)) - return children[1].get(); - - return 0; -} - -AccessibilityObject* AccessibilityRenderObject::mathOverObject() -{ - if (!isMathUnderOver() || !node()) - return 0; - - const auto& children = this->children(); - if (children.size() < 2) - return 0; - - if (node()->hasTagName(MathMLNames::moverTag)) - return children[1].get(); - if (node()->hasTagName(MathMLNames::munderoverTag)) - return children[2].get(); - - return 0; -} - -AccessibilityObject* AccessibilityRenderObject::mathBaseObject() -{ - if (!isMathSubscriptSuperscript() && !isMathUnderOver() && !isMathMultiscript()) - return 0; - - const auto& children = this->children(); - // The base object in question is always the first child. - if (children.size() > 0) - return children[0].get(); - - return 0; -} - -AccessibilityObject* AccessibilityRenderObject::mathSubscriptObject() -{ - if (!isMathSubscriptSuperscript() || !node()) - return 0; - - const auto& children = this->children(); - if (children.size() < 2) - return 0; - - if (node()->hasTagName(MathMLNames::msubTag) || node()->hasTagName(MathMLNames::msubsupTag)) - return children[1].get(); - - return 0; -} - -AccessibilityObject* AccessibilityRenderObject::mathSuperscriptObject() -{ - if (!isMathSubscriptSuperscript() || !node()) - return 0; - - const auto& children = this->children(); - unsigned count = children.size(); - - if (count >= 2 && node()->hasTagName(MathMLNames::msupTag)) - return children[1].get(); - - if (count >= 3 && node()->hasTagName(MathMLNames::msubsupTag)) - return children[2].get(); - - return 0; -} - -String AccessibilityRenderObject::mathFencedOpenString() const -{ - if (!isMathFenced()) - return String(); - - return getAttribute(MathMLNames::openAttr); -} - -String AccessibilityRenderObject::mathFencedCloseString() const -{ - if (!isMathFenced()) - return String(); - - return getAttribute(MathMLNames::closeAttr); -} - -void AccessibilityRenderObject::mathPrescripts(AccessibilityMathMultiscriptPairs& prescripts) -{ - if (!isMathMultiscript() || !node()) - return; - - bool foundPrescript = false; - std::pair<AccessibilityObject*, AccessibilityObject*> prescriptPair; - for (Node* child = node()->firstChild(); child; child = child->nextSibling()) { - if (foundPrescript) { - AccessibilityObject* axChild = axObjectCache()->getOrCreate(child); - if (axChild && axChild->isMathElement()) { - if (!prescriptPair.first) - prescriptPair.first = axChild; - else { - prescriptPair.second = axChild; - prescripts.append(prescriptPair); - prescriptPair.first = 0; - prescriptPair.second = 0; - } - } - } else if (child->hasTagName(MathMLNames::mprescriptsTag)) - foundPrescript = true; - } - - // Handle the odd number of pre scripts case. - if (prescriptPair.first) - prescripts.append(prescriptPair); -} - -void AccessibilityRenderObject::mathPostscripts(AccessibilityMathMultiscriptPairs& postscripts) -{ - if (!isMathMultiscript() || !node()) - return; - - // In Multiscripts, the post-script elements start after the first element (which is the base) - // and continue until a <mprescripts> tag is found - std::pair<AccessibilityObject*, AccessibilityObject*> postscriptPair; - bool foundBaseElement = false; - for (Node* child = node()->firstChild(); child; child = child->nextSibling()) { - if (child->hasTagName(MathMLNames::mprescriptsTag)) - break; - - AccessibilityObject* axChild = axObjectCache()->getOrCreate(child); - if (axChild && axChild->isMathElement()) { - if (!foundBaseElement) - foundBaseElement = true; - else if (!postscriptPair.first) - postscriptPair.first = axChild; - else { - postscriptPair.second = axChild; - postscripts.append(postscriptPair); - postscriptPair.first = 0; - postscriptPair.second = 0; - } - } - } - - // Handle the odd number of post scripts case. - if (postscriptPair.first) - postscripts.append(postscriptPair); -} - -int AccessibilityRenderObject::mathLineThickness() const -{ - if (!isMathFraction()) - return -1; - - return toRenderMathMLFraction(m_renderer)->lineThickness(); -} - #endif } // namespace WebCore |