/* * Copyright (C) 2012, Google Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 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 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. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "AccessibilityNodeObject.h" #include "AXObjectCache.h" #include "AccessibilityImageMapLink.h" #include "AccessibilityList.h" #include "AccessibilityListBox.h" #include "AccessibilitySpinButton.h" #include "AccessibilityTable.h" #include "ElementIterator.h" #include "EventNames.h" #include "FloatRect.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameSelection.h" #include "FrameView.h" #include "HTMLAreaElement.h" #include "HTMLCanvasElement.h" #include "HTMLDetailsElement.h" #include "HTMLFieldSetElement.h" #include "HTMLFormElement.h" #include "HTMLFrameElementBase.h" #include "HTMLImageElement.h" #include "HTMLInputElement.h" #include "HTMLLabelElement.h" #include "HTMLLegendElement.h" #include "HTMLMapElement.h" #include "HTMLNames.h" #include "HTMLOptGroupElement.h" #include "HTMLOptionElement.h" #include "HTMLOptionsCollection.h" #include "HTMLParserIdioms.h" #include "HTMLPlugInImageElement.h" #include "HTMLSelectElement.h" #include "HTMLTextAreaElement.h" #include "HTMLTextFormControlElement.h" #include "HitTestRequest.h" #include "HitTestResult.h" #include "LabelableElement.h" #include "LocalizedStrings.h" #include "MathMLElement.h" #include "MathMLNames.h" #include "NodeList.h" #include "NodeTraversal.h" #include "Page.h" #include "ProgressTracker.h" #include "RenderImage.h" #include "RenderView.h" #include "SVGElement.h" #include "SVGNames.h" #include "Text.h" #include "TextControlInnerElements.h" #include "UserGestureIndicator.h" #include "VisibleUnits.h" #include "Widget.h" #include "htmlediting.h" #include #include #include namespace WebCore { using namespace HTMLNames; static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr); AccessibilityNodeObject::AccessibilityNodeObject(Node* node) : AccessibilityObject() , m_ariaRole(UnknownRole) , m_childrenDirty(false) , m_roleForMSAA(UnknownRole) #ifndef NDEBUG , m_initialized(false) #endif , m_node(node) { } AccessibilityNodeObject::~AccessibilityNodeObject() { ASSERT(isDetached()); } void AccessibilityNodeObject::init() { #ifndef NDEBUG ASSERT(!m_initialized); m_initialized = true; #endif m_role = determineAccessibilityRole(); } Ref AccessibilityNodeObject::create(Node* node) { return adoptRef(*new AccessibilityNodeObject(node)); } void AccessibilityNodeObject::detach(AccessibilityDetachmentType detachmentType, AXObjectCache* cache) { // AccessibilityObject calls clearChildren. AccessibilityObject::detach(detachmentType, cache); m_node = nullptr; } void AccessibilityNodeObject::childrenChanged() { // This method is meant as a quick way of marking a portion of the accessibility tree dirty. if (!node() && !renderer()) return; AXObjectCache* cache = axObjectCache(); if (!cache) return; cache->postNotification(this, document(), AXObjectCache::AXChildrenChanged); // Go up the accessibility parent chain, but only if the element already exists. This method is // called during render layouts, minimal work should be done. // If AX elements are created now, they could interrogate the render tree while it's in a funky state. // At the same time, process ARIA live region changes. for (AccessibilityObject* parent = this; parent; parent = parent->parentObjectIfExists()) { parent->setNeedsToUpdateChildren(); // These notifications always need to be sent because screenreaders are reliant on them to perform. // In other words, they need to be sent even when the screen reader has not accessed this live region since the last update. // If this element supports ARIA live regions, then notify the AT of changes. // Sometimes this function can be called many times within a short period of time, leading to posting too many AXLiveRegionChanged // notifications. To fix this, we used a timer to make sure we only post one notification for the children changes within a pre-defined // time interval. if (parent->supportsARIALiveRegion()) cache->postLiveRegionChangeNotification(parent); // If this element is an ARIA text control, notify the AT of changes. if (parent->isNonNativeTextControl()) cache->postNotification(parent, parent->document(), AXObjectCache::AXValueChanged); } } void AccessibilityNodeObject::updateAccessibilityRole() { bool ignoredStatus = accessibilityIsIgnored(); m_role = determineAccessibilityRole(); // The AX hierarchy only needs to be updated if the ignored status of an element has changed. if (ignoredStatus != accessibilityIsIgnored()) childrenChanged(); } AccessibilityObject* AccessibilityNodeObject::firstChild() const { if (!node()) return nullptr; Node* firstChild = node()->firstChild(); if (!firstChild) return nullptr; return axObjectCache()->getOrCreate(firstChild); } AccessibilityObject* AccessibilityNodeObject::lastChild() const { if (!node()) return nullptr; Node* lastChild = node()->lastChild(); if (!lastChild) return nullptr; return axObjectCache()->getOrCreate(lastChild); } AccessibilityObject* AccessibilityNodeObject::previousSibling() const { if (!node()) return nullptr; Node* previousSibling = node()->previousSibling(); if (!previousSibling) return nullptr; return axObjectCache()->getOrCreate(previousSibling); } AccessibilityObject* AccessibilityNodeObject::nextSibling() const { if (!node()) return nullptr; Node* nextSibling = node()->nextSibling(); if (!nextSibling) return nullptr; return axObjectCache()->getOrCreate(nextSibling); } AccessibilityObject* AccessibilityNodeObject::parentObjectIfExists() const { return parentObject(); } AccessibilityObject* AccessibilityNodeObject::parentObject() const { if (!node()) return nullptr; Node* parentObj = node()->parentNode(); if (!parentObj) return nullptr; if (AXObjectCache* cache = axObjectCache()) return cache->getOrCreate(parentObj); return nullptr; } LayoutRect AccessibilityNodeObject::elementRect() const { return boundingBoxRect(); } LayoutRect AccessibilityNodeObject::boundingBoxRect() const { // AccessibilityNodeObjects have no mechanism yet to return a size or position. // For now, let's return the position of the ancestor that does have a position, // and make it the width of that parent, and about the height of a line of text, so that it's clear the object is a child of the parent. LayoutRect boundingBox; for (AccessibilityObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) { if (positionProvider->isAccessibilityRenderObject()) { LayoutRect parentRect = positionProvider->elementRect(); boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat())))); boundingBox.setLocation(parentRect.location()); break; } } return boundingBox; } void AccessibilityNodeObject::setNode(Node* node) { m_node = node; } Document* AccessibilityNodeObject::document() const { if (!node()) return nullptr; return &node()->document(); } AccessibilityRole AccessibilityNodeObject::determineAccessibilityRole() { if (!node()) return UnknownRole; if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole) return m_ariaRole; if (node()->isLink()) return WebCoreLinkRole; if (node()->isTextNode()) return StaticTextRole; if (node()->hasTagName(buttonTag)) return buttonRoleType(); if (is(*node())) { HTMLInputElement& input = downcast(*node()); if (input.isCheckbox()) return CheckBoxRole; if (input.isRadioButton()) return RadioButtonRole; if (input.isTextButton()) return buttonRoleType(); if (input.isRangeControl()) return SliderRole; if (input.isInputTypeHidden()) return IgnoredRole; if (input.isSearchField()) return SearchFieldRole; #if ENABLE(INPUT_TYPE_COLOR) if (input.isColorControl()) return ColorWellRole; #endif return TextFieldRole; } if (node()->hasTagName(selectTag)) { HTMLSelectElement& selectElement = downcast(*node()); return selectElement.multiple() ? ListBoxRole : PopUpButtonRole; } if (is(*node())) return TextAreaRole; if (headingLevel()) return HeadingRole; if (node()->hasTagName(blockquoteTag)) return BlockquoteRole; if (node()->hasTagName(divTag)) return DivRole; if (node()->hasTagName(pTag)) return ParagraphRole; if (is(*node())) return LabelRole; if (is(*node()) && downcast(*node()).isFocusable()) return GroupRole; return UnknownRole; } void AccessibilityNodeObject::insertChild(AccessibilityObject* child, unsigned index) { if (!child) return; // If the parent is asking for this child's children, then either it's the first time (and clearing is a no-op), // or its visibility has changed. In the latter case, this child may have a stale child cached. // This can prevent aria-hidden changes from working correctly. Hence, whenever a parent is getting children, ensure data is not stale. child->clearChildren(); if (child->accessibilityIsIgnored()) { const auto& children = child->children(); size_t length = children.size(); for (size_t i = 0; i < length; ++i) m_children.insert(index + i, children[i]); } else { ASSERT(child->parentObject() == this); m_children.insert(index, child); } } void AccessibilityNodeObject::addChild(AccessibilityObject* child) { insertChild(child, m_children.size()); } void AccessibilityNodeObject::addChildren() { // If the need to add more children in addition to existing children arises, // childrenChanged should have been called, leaving the object with no children. ASSERT(!m_haveChildren); if (!m_node) return; m_haveChildren = true; // The only time we add children from the DOM tree to a node with a renderer is when it's a canvas. if (renderer() && !m_node->hasTagName(canvasTag)) return; for (Node* child = m_node->firstChild(); child; child = child->nextSibling()) addChild(axObjectCache()->getOrCreate(child)); } bool AccessibilityNodeObject::canHaveChildren() const { // If this is an AccessibilityRenderObject, then it's okay if this object // doesn't have a node - there are some renderers that don't have associated // nodes, like scroll areas and css-generated text. if (!node() && !isAccessibilityRenderObject()) return false; // When