/* * 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 "AXLogger.h" #include "AXObjectCache.h" #include "AccessibilityImageMapLink.h" #include "AccessibilityList.h" #include "AccessibilityListBox.h" #include "AccessibilitySpinButton.h" #include "AccessibilityTable.h" #include "Editing.h" #include "ElementIterator.h" #include "Event.h" #include "EventNames.h" #include "FloatRect.h" #include "Frame.h" #include "FrameLoader.h" #include "FrameSelection.h" #include "FrameView.h" #include "HTMLCanvasElement.h" #include "HTMLDetailsElement.h" #include "HTMLFieldSetElement.h" #include "HTMLFormElement.h" #include "HTMLImageElement.h" #include "HTMLInputElement.h" #include "HTMLLabelElement.h" #include "HTMLLegendElement.h" #include "HTMLNames.h" #include "HTMLOptionElement.h" #include "HTMLParserIdioms.h" #include "HTMLSelectElement.h" #include "HTMLTextAreaElement.h" #include "HTMLTextFormControlElement.h" #include "KeyboardEvent.h" #include "LabelableElement.h" #include "LocalizedStrings.h" #include "MathMLElement.h" #include "MathMLNames.h" #include "NodeList.h" #include "NodeTraversal.h" #include "ProgressTracker.h" #include "RenderImage.h" #include "RenderView.h" #include "SVGElement.h" #include "Text.h" #include "TextControlInnerElements.h" #include "UserGestureIndicator.h" #include "VisibleUnits.h" #include "Widget.h" #include #include #include namespace WebCore { using namespace HTMLNames; static String accessibleNameForNode(Node* node, Node* labelledbyNode = nullptr); AccessibilityNodeObject::AccessibilityNodeObject(Node* node) : AccessibilityObject() , 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::detachRemoteParts(AccessibilityDetachmentType detachmentType) { // AccessibilityObject calls clearChildren. AccessibilityObject::detachRemoteParts(detachmentType); 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); // Should make the sub tree dirty so that everything below will be updated correctly. this->setNeedsToUpdateSubtree(); bool shouldStopUpdatingParent = false; // 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()) { if (!shouldStopUpdatingParent) 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->supportsLiveRegion()) 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); // Do not let the parent that's above the editable ancestor update its children // since we already notify the AT of changes. shouldStopUpdatingParent = true; } } } 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; auto objectCache = axObjectCache(); return objectCache ? objectCache->getOrCreate(firstChild) : nullptr; } AccessibilityObject* AccessibilityNodeObject::lastChild() const { if (!node()) return nullptr; Node* lastChild = node()->lastChild(); if (!lastChild) return nullptr; auto objectCache = axObjectCache(); return objectCache ? objectCache->getOrCreate(lastChild) : nullptr; } AccessibilityObject* AccessibilityNodeObject::previousSibling() const { if (!node()) return nullptr; Node* previousSibling = node()->previousSibling(); if (!previousSibling) return nullptr; auto objectCache = axObjectCache(); return objectCache ? objectCache->getOrCreate(previousSibling) : nullptr; } AccessibilityObject* AccessibilityNodeObject::nextSibling() const { if (!node()) return nullptr; Node* nextSibling = node()->nextSibling(); if (!nextSibling) return nullptr; auto objectCache = axObjectCache(); return objectCache ? objectCache->getOrCreate(nextSibling) : nullptr; } 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() { AXTRACE("AccessibilityNodeObject::determineAccessibilityRole"); if (!node()) return AccessibilityRole::Unknown; if ((m_ariaRole = determineAriaRoleAttribute()) != AccessibilityRole::Unknown) return m_ariaRole; if (node()->isLink()) return AccessibilityRole::WebCoreLink; if (node()->isTextNode()) return AccessibilityRole::StaticText; if (node()->hasTagName(buttonTag)) return buttonRoleType(); if (is(*node())) { HTMLInputElement& input = downcast(*node()); if (input.isCheckbox()) return AccessibilityRole::CheckBox; if (input.isRadioButton()) return AccessibilityRole::RadioButton; if (input.isTextButton()) return buttonRoleType(); if (input.isRangeControl()) return AccessibilityRole::Slider; if (input.isInputTypeHidden()) return AccessibilityRole::Ignored; if (input.isSearchField()) return AccessibilityRole::SearchField; #if ENABLE(INPUT_TYPE_COLOR) if (input.isColorControl()) return AccessibilityRole::ColorWell; #endif return AccessibilityRole::TextField; } if (node()->hasTagName(selectTag)) { HTMLSelectElement& selectElement = downcast(*node()); return selectElement.multiple() ? AccessibilityRole::ListBox : AccessibilityRole::PopUpButton; } if (is(*node())) return AccessibilityRole::TextArea; if (headingLevel()) return AccessibilityRole::Heading; if (node()->hasTagName(blockquoteTag)) return AccessibilityRole::Blockquote; if (node()->hasTagName(divTag)) return AccessibilityRole::Div; if (node()->hasTagName(pTag)) return AccessibilityRole::Paragraph; if (is(*node())) return AccessibilityRole::Label; if (is(*node()) && downcast(*node()).isFocusable()) return AccessibilityRole::Group; return AccessibilityRole::Unknown; } 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; auto objectCache = axObjectCache(); if (!objectCache) return; for (Node* child = m_node->firstChild(); child; child = child->nextSibling()) addChild(objectCache->getOrCreate(child)); m_subtreeDirty = false; } 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