| Index: Source/core/accessibility/AXNodeObject.cpp
|
| diff --git a/Source/core/accessibility/AXNodeObject.cpp b/Source/core/accessibility/AXNodeObject.cpp
|
| deleted file mode 100644
|
| index 917f64a04c8d02f4d37d2d1937f2474c6f17f19c..0000000000000000000000000000000000000000
|
| --- a/Source/core/accessibility/AXNodeObject.cpp
|
| +++ /dev/null
|
| @@ -1,1783 +0,0 @@
|
| -/*
|
| -* 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 Computer, 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 "core/accessibility/AXNodeObject.h"
|
| -
|
| -#include "core/InputTypeNames.h"
|
| -#include "core/accessibility/AXObjectCacheImpl.h"
|
| -#include "core/dom/NodeTraversal.h"
|
| -#include "core/dom/Text.h"
|
| -#include "core/html/HTMLDListElement.h"
|
| -#include "core/html/HTMLFieldSetElement.h"
|
| -#include "core/html/HTMLFrameElementBase.h"
|
| -#include "core/html/HTMLInputElement.h"
|
| -#include "core/html/HTMLLabelElement.h"
|
| -#include "core/html/HTMLLegendElement.h"
|
| -#include "core/html/HTMLPlugInElement.h"
|
| -#include "core/html/HTMLSelectElement.h"
|
| -#include "core/html/HTMLTextAreaElement.h"
|
| -#include "core/rendering/RenderObject.h"
|
| -#include "platform/UserGestureIndicator.h"
|
| -#include "wtf/text/StringBuilder.h"
|
| -
|
| -
|
| -namespace blink {
|
| -
|
| -using namespace HTMLNames;
|
| -
|
| -AXNodeObject::AXNodeObject(Node* node)
|
| - : AXObject()
|
| - , m_ariaRole(UnknownRole)
|
| - , m_childrenDirty(false)
|
| -#if ENABLE(ASSERT)
|
| - , m_initialized(false)
|
| -#endif
|
| - , m_node(node)
|
| -{
|
| -}
|
| -
|
| -PassRefPtr<AXNodeObject> AXNodeObject::create(Node* node)
|
| -{
|
| - return adoptRef(new AXNodeObject(node));
|
| -}
|
| -
|
| -AXNodeObject::~AXNodeObject()
|
| -{
|
| - ASSERT(isDetached());
|
| -}
|
| -
|
| -// This function implements the ARIA accessible name as described by the Mozilla
|
| -// ARIA Implementer's Guide.
|
| -static String accessibleNameForNode(Node* node)
|
| -{
|
| - if (!node)
|
| - return String();
|
| -
|
| - if (node->isTextNode())
|
| - return toText(node)->data();
|
| -
|
| - if (isHTMLInputElement(*node))
|
| - return toHTMLInputElement(*node).value();
|
| -
|
| - if (node->isHTMLElement()) {
|
| - const AtomicString& alt = toHTMLElement(node)->getAttribute(altAttr);
|
| - if (!alt.isEmpty())
|
| - return alt;
|
| - }
|
| -
|
| - return String();
|
| -}
|
| -
|
| -String AXNodeObject::accessibilityDescriptionForElements(WillBeHeapVector<RawPtrWillBeMember<Element> > &elements) const
|
| -{
|
| - StringBuilder builder;
|
| - unsigned size = elements.size();
|
| - for (unsigned i = 0; i < size; ++i) {
|
| - Element* idElement = elements[i];
|
| -
|
| - builder.append(accessibleNameForNode(idElement));
|
| - for (Node& n : NodeTraversal::descendantsOf(*idElement))
|
| - builder.append(accessibleNameForNode(&n));
|
| -
|
| - if (i != size - 1)
|
| - builder.append(' ');
|
| - }
|
| - return builder.toString();
|
| -}
|
| -
|
| -void AXNodeObject::alterSliderValue(bool increase)
|
| -{
|
| - if (roleValue() != SliderRole)
|
| - return;
|
| -
|
| - if (!getAttribute(stepAttr).isEmpty())
|
| - changeValueByStep(increase);
|
| - else
|
| - changeValueByPercent(increase ? 5 : -5);
|
| -}
|
| -
|
| -String AXNodeObject::ariaAccessibilityDescription() const
|
| -{
|
| - String ariaLabeledBy = ariaLabeledByAttribute();
|
| - if (!ariaLabeledBy.isEmpty())
|
| - return ariaLabeledBy;
|
| -
|
| - const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
|
| - if (!ariaLabel.isEmpty())
|
| - return ariaLabel;
|
| -
|
| - return String();
|
| -}
|
| -
|
| -
|
| -void AXNodeObject::ariaLabeledByElements(WillBeHeapVector<RawPtrWillBeMember<Element> >& elements) const
|
| -{
|
| - elementsFromAttribute(elements, aria_labeledbyAttr);
|
| - if (!elements.size())
|
| - elementsFromAttribute(elements, aria_labelledbyAttr);
|
| -}
|
| -
|
| -void AXNodeObject::changeValueByStep(bool increase)
|
| -{
|
| - float step = stepValueForRange();
|
| - float value = valueForRange();
|
| -
|
| - value += increase ? step : -step;
|
| -
|
| - setValue(String::number(value));
|
| -
|
| - axObjectCache()->postNotification(node(), AXObjectCacheImpl::AXValueChanged, true);
|
| -}
|
| -
|
| -bool AXNodeObject::computeAccessibilityIsIgnored() const
|
| -{
|
| -#if ENABLE(ASSERT)
|
| - // Double-check that an AXObject is never accessed before
|
| - // it's been initialized.
|
| - ASSERT(m_initialized);
|
| -#endif
|
| -
|
| - // If this element is within a parent that cannot have children, it should not be exposed.
|
| - if (isDescendantOfBarrenParent())
|
| - return true;
|
| -
|
| - // Ignore labels that are already referenced by a control's title UI element.
|
| - AXObject* controlObject = correspondingControlForLabelElement();
|
| - if (controlObject && !controlObject->exposesTitleUIElement() && controlObject->isCheckboxOrRadio())
|
| - return true;
|
| -
|
| - return m_role == UnknownRole;
|
| -}
|
| -
|
| -AccessibilityRole AXNodeObject::determineAccessibilityRole()
|
| -{
|
| - if (!node())
|
| - return UnknownRole;
|
| -
|
| - m_ariaRole = determineAriaRoleAttribute();
|
| -
|
| - AccessibilityRole ariaRole = ariaRoleAttribute();
|
| - if (ariaRole != UnknownRole)
|
| - return ariaRole;
|
| -
|
| - if (node()->isLink())
|
| - return LinkRole;
|
| - if (node()->isTextNode())
|
| - return StaticTextRole;
|
| - if (isHTMLButtonElement(*node()))
|
| - return buttonRoleType();
|
| - if (isHTMLDetailsElement(*node()))
|
| - return DetailsRole;
|
| - if (isHTMLSummaryElement(*node())) {
|
| - if (node()->parentNode() && isHTMLDetailsElement(node()->parentNode()))
|
| - return DisclosureTriangleRole;
|
| - return UnknownRole;
|
| - }
|
| -
|
| - if (isHTMLInputElement(*node())) {
|
| - HTMLInputElement& input = toHTMLInputElement(*node());
|
| - const AtomicString& type = input.type();
|
| - if (type == InputTypeNames::button) {
|
| - if ((node()->parentNode() && isHTMLMenuElement(node()->parentNode())) || (parentObject() && parentObject()->roleValue() == MenuRole))
|
| - return MenuItemRole;
|
| - return buttonRoleType();
|
| - }
|
| - if (type == InputTypeNames::checkbox) {
|
| - if ((node()->parentNode() && isHTMLMenuElement(node()->parentNode())) || (parentObject() && parentObject()->roleValue() == MenuRole))
|
| - return MenuItemCheckBoxRole;
|
| - return CheckBoxRole;
|
| - }
|
| - if (type == InputTypeNames::date)
|
| - return DateRole;
|
| - if (type == InputTypeNames::datetime
|
| - || type == InputTypeNames::datetime_local
|
| - || type == InputTypeNames::month
|
| - || type == InputTypeNames::week)
|
| - return DateTimeRole;
|
| - if (type == InputTypeNames::radio) {
|
| - if ((node()->parentNode() && isHTMLMenuElement(node()->parentNode())) || (parentObject() && parentObject()->roleValue() == MenuRole))
|
| - return MenuItemRadioRole;
|
| - return RadioButtonRole;
|
| - }
|
| - if (input.isTextButton())
|
| - return buttonRoleType();
|
| - if (type == InputTypeNames::range)
|
| - return SliderRole;
|
| - if (type == InputTypeNames::color)
|
| - return ColorWellRole;
|
| - if (type == InputTypeNames::time)
|
| - return TimeRole;
|
| - return TextFieldRole;
|
| - }
|
| - if (isHTMLSelectElement(*node())) {
|
| - HTMLSelectElement& selectElement = toHTMLSelectElement(*node());
|
| - return selectElement.multiple() ? ListBoxRole : PopUpButtonRole;
|
| - }
|
| - if (isHTMLTextAreaElement(*node()))
|
| - return TextAreaRole;
|
| - if (headingLevel())
|
| - return HeadingRole;
|
| - if (isHTMLDivElement(*node()))
|
| - return DivRole;
|
| - if (isHTMLMeterElement(*node()))
|
| - return MeterRole;
|
| - if (isHTMLOutputElement(*node()))
|
| - return StatusRole;
|
| - if (isHTMLParagraphElement(*node()))
|
| - return ParagraphRole;
|
| - if (isHTMLLabelElement(*node()))
|
| - return LabelRole;
|
| - if (isHTMLRubyElement(*node()))
|
| - return RubyRole;
|
| - if (isHTMLDListElement(*node()))
|
| - return DescriptionListRole;
|
| - if (node()->isElementNode() && node()->hasTagName(blockquoteTag))
|
| - return BlockquoteRole;
|
| - if (node()->isElementNode() && node()->hasTagName(figcaptionTag))
|
| - return FigcaptionRole;
|
| - if (node()->isElementNode() && node()->hasTagName(figureTag))
|
| - return FigureRole;
|
| - if (node()->isElementNode() && toElement(node())->isFocusable())
|
| - return GroupRole;
|
| - if (isHTMLAnchorElement(*node()) && isClickable())
|
| - return LinkRole;
|
| - if (isHTMLIFrameElement(*node()))
|
| - return IframeRole;
|
| - if (isEmbeddedObject())
|
| - return EmbeddedObjectRole;
|
| -
|
| - return UnknownRole;
|
| -}
|
| -
|
| -AccessibilityRole AXNodeObject::determineAriaRoleAttribute() const
|
| -{
|
| - const AtomicString& ariaRole = getAttribute(roleAttr);
|
| - if (ariaRole.isNull() || ariaRole.isEmpty())
|
| - return UnknownRole;
|
| -
|
| - AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole);
|
| -
|
| - // ARIA states if an item can get focus, it should not be presentational.
|
| - if ((role == NoneRole || role == PresentationalRole) && canSetFocusAttribute())
|
| - return UnknownRole;
|
| -
|
| - if (role == ButtonRole)
|
| - role = buttonRoleType();
|
| -
|
| - if (role == TextAreaRole && !ariaIsMultiline())
|
| - role = TextFieldRole;
|
| -
|
| - role = remapAriaRoleDueToParent(role);
|
| -
|
| - if (role)
|
| - return role;
|
| -
|
| - return UnknownRole;
|
| -}
|
| -
|
| -void AXNodeObject::elementsFromAttribute(WillBeHeapVector<RawPtrWillBeMember<Element> >& elements, const QualifiedName& attribute) const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node || !node->isElementNode())
|
| - return;
|
| -
|
| - TreeScope& scope = node->treeScope();
|
| -
|
| - String idList = getAttribute(attribute).string();
|
| - if (idList.isEmpty())
|
| - return;
|
| -
|
| - idList.replace('\n', ' ');
|
| - Vector<String> idVector;
|
| - idList.split(' ', idVector);
|
| -
|
| - unsigned size = idVector.size();
|
| - for (unsigned i = 0; i < size; ++i) {
|
| - AtomicString idName(idVector[i]);
|
| - Element* idElement = scope.getElementById(idName);
|
| - if (idElement)
|
| - elements.append(idElement);
|
| - }
|
| -}
|
| -
|
| -// If you call node->hasEditableStyle() since that will return true if an ancestor is editable.
|
| -// This only returns true if this is the element that actually has the contentEditable attribute set.
|
| -bool AXNodeObject::hasContentEditableAttributeSet() const
|
| -{
|
| - if (!hasAttribute(contenteditableAttr))
|
| - return false;
|
| - const AtomicString& contentEditableValue = getAttribute(contenteditableAttr);
|
| - // Both "true" (case-insensitive) and the empty string count as true.
|
| - return contentEditableValue.isEmpty() || equalIgnoringCase(contentEditableValue, "true");
|
| -}
|
| -
|
| -bool AXNodeObject::isDescendantOfBarrenParent() const
|
| -{
|
| - for (AXObject* object = parentObject(); object; object = object->parentObject()) {
|
| - if (!object->canHaveChildren())
|
| - return true;
|
| - }
|
| -
|
| - return false;
|
| -}
|
| -
|
| -bool AXNodeObject::isGenericFocusableElement() const
|
| -{
|
| - if (!canSetFocusAttribute())
|
| - return false;
|
| -
|
| - // If it's a control, it's not generic.
|
| - if (isControl())
|
| - return false;
|
| -
|
| - // If it has an aria role, it's not generic.
|
| - if (m_ariaRole != UnknownRole)
|
| - return false;
|
| -
|
| - // If the content editable attribute is set on this element, that's the reason
|
| - // it's focusable, and existing logic should handle this case already - so it's not a
|
| - // generic focusable element.
|
| -
|
| - if (hasContentEditableAttributeSet())
|
| - return false;
|
| -
|
| - // The web area and body element are both focusable, but existing logic handles these
|
| - // cases already, so we don't need to include them here.
|
| - if (roleValue() == WebAreaRole)
|
| - return false;
|
| - if (isHTMLBodyElement(node()))
|
| - return false;
|
| -
|
| - // An SVG root is focusable by default, but it's probably not interactive, so don't
|
| - // include it. It can still be made accessible by giving it an ARIA role.
|
| - if (roleValue() == SVGRootRole)
|
| - return false;
|
| -
|
| - return true;
|
| -}
|
| -
|
| -HTMLLabelElement* AXNodeObject::labelForElement(Element* element) const
|
| -{
|
| - if (!element->isHTMLElement() || !toHTMLElement(element)->isLabelable())
|
| - return 0;
|
| -
|
| - const AtomicString& id = element->getIdAttribute();
|
| - if (!id.isEmpty()) {
|
| - if (HTMLLabelElement* label = element->treeScope().labelElementForId(id))
|
| - return label;
|
| - }
|
| -
|
| - return Traversal<HTMLLabelElement>::firstAncestor(*element);
|
| -}
|
| -
|
| -AXObject* AXNodeObject::menuButtonForMenu() const
|
| -{
|
| - Element* menuItem = menuItemElementForMenu();
|
| -
|
| - if (menuItem) {
|
| - // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem
|
| - AXObject* menuItemAX = axObjectCache()->getOrCreate(menuItem);
|
| - if (menuItemAX && menuItemAX->isMenuButton())
|
| - return menuItemAX;
|
| - }
|
| - return 0;
|
| -}
|
| -
|
| -static Element* siblingWithAriaRole(String role, Node* node)
|
| -{
|
| - Node* parent = node->parentNode();
|
| - if (!parent)
|
| - return 0;
|
| -
|
| - for (Element* sibling = ElementTraversal::firstChild(*parent); sibling; sibling = ElementTraversal::nextSibling(*sibling)) {
|
| - const AtomicString& siblingAriaRole = sibling->getAttribute(roleAttr);
|
| - if (equalIgnoringCase(siblingAriaRole, role))
|
| - return sibling;
|
| - }
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -Element* AXNodeObject::menuItemElementForMenu() const
|
| -{
|
| - if (ariaRoleAttribute() != MenuRole)
|
| - return 0;
|
| -
|
| - return siblingWithAriaRole("menuitem", node());
|
| -}
|
| -
|
| -Element* AXNodeObject::mouseButtonListener() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return 0;
|
| -
|
| - // check if our parent is a mouse button listener
|
| - if (!node->isElementNode())
|
| - node = node->parentElement();
|
| -
|
| - if (!node)
|
| - return 0;
|
| -
|
| - // FIXME: Do the continuation search like anchorElement does
|
| - for (Element* element = toElement(node); element; element = element->parentElement()) {
|
| - if (element->getAttributeEventListener(EventTypeNames::click) || element->getAttributeEventListener(EventTypeNames::mousedown) || element->getAttributeEventListener(EventTypeNames::mouseup))
|
| - return element;
|
| - }
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -AccessibilityRole AXNodeObject::remapAriaRoleDueToParent(AccessibilityRole role) const
|
| -{
|
| - // Some objects change their role based on their parent.
|
| - // However, asking for the unignoredParent calls accessibilityIsIgnored(), which can trigger a loop.
|
| - // While inside the call stack of creating an element, we need to avoid accessibilityIsIgnored().
|
| - // https://bugs.webkit.org/show_bug.cgi?id=65174
|
| -
|
| - if (role != ListBoxOptionRole && role != MenuItemRole)
|
| - return role;
|
| -
|
| - for (AXObject* parent = parentObject(); parent && !parent->accessibilityIsIgnored(); parent = parent->parentObject()) {
|
| - AccessibilityRole parentAriaRole = parent->ariaRoleAttribute();
|
| -
|
| - // Selects and listboxes both have options as child roles, but they map to different roles within WebCore.
|
| - if (role == ListBoxOptionRole && parentAriaRole == MenuRole)
|
| - return MenuItemRole;
|
| - // An aria "menuitem" may map to MenuButton or MenuItem depending on its parent.
|
| - if (role == MenuItemRole && parentAriaRole == GroupRole)
|
| - return MenuButtonRole;
|
| -
|
| - // If the parent had a different role, then we don't need to continue searching up the chain.
|
| - if (parentAriaRole)
|
| - break;
|
| - }
|
| -
|
| - return role;
|
| -}
|
| -
|
| -void AXNodeObject::init()
|
| -{
|
| -#if ENABLE(ASSERT)
|
| - ASSERT(!m_initialized);
|
| - m_initialized = true;
|
| -#endif
|
| - m_role = determineAccessibilityRole();
|
| -}
|
| -
|
| -void AXNodeObject::detach()
|
| -{
|
| - clearChildren();
|
| - AXObject::detach();
|
| - m_node = 0;
|
| -}
|
| -
|
| -bool AXNodeObject::isAnchor() const
|
| -{
|
| - return !isNativeImage() && isLink();
|
| -}
|
| -
|
| -bool AXNodeObject::isControl() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return false;
|
| -
|
| - return ((node->isElementNode() && toElement(node)->isFormControlElement())
|
| - || AXObject::isARIAControl(ariaRoleAttribute()));
|
| -}
|
| -
|
| -bool AXNodeObject::isEmbeddedObject() const
|
| -{
|
| - return isHTMLPlugInElement(node());
|
| -}
|
| -
|
| -bool AXNodeObject::isFieldset() const
|
| -{
|
| - return isHTMLFieldSetElement(node());
|
| -}
|
| -
|
| -bool AXNodeObject::isHeading() const
|
| -{
|
| - return roleValue() == HeadingRole;
|
| -}
|
| -
|
| -bool AXNodeObject::isHovered() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return false;
|
| -
|
| - return node->hovered();
|
| -}
|
| -
|
| -bool AXNodeObject::isImage() const
|
| -{
|
| - return roleValue() == ImageRole;
|
| -}
|
| -
|
| -bool AXNodeObject::isImageButton() const
|
| -{
|
| - return isNativeImage() && isButton();
|
| -}
|
| -
|
| -bool AXNodeObject::isInputImage() const
|
| -{
|
| - Node* node = this->node();
|
| - if (roleValue() == ButtonRole && isHTMLInputElement(node))
|
| - return toHTMLInputElement(*node).type() == InputTypeNames::image;
|
| -
|
| - return false;
|
| -}
|
| -
|
| -bool AXNodeObject::isLink() const
|
| -{
|
| - return roleValue() == LinkRole;
|
| -}
|
| -
|
| -bool AXNodeObject::isMenu() const
|
| -{
|
| - return roleValue() == MenuRole;
|
| -}
|
| -
|
| -bool AXNodeObject::isMenuButton() const
|
| -{
|
| - return roleValue() == MenuButtonRole;
|
| -}
|
| -
|
| -bool AXNodeObject::isMultiSelectable() const
|
| -{
|
| - const AtomicString& ariaMultiSelectable = getAttribute(aria_multiselectableAttr);
|
| - if (equalIgnoringCase(ariaMultiSelectable, "true"))
|
| - return true;
|
| - if (equalIgnoringCase(ariaMultiSelectable, "false"))
|
| - return false;
|
| -
|
| - return isHTMLSelectElement(node()) && toHTMLSelectElement(*node()).multiple();
|
| -}
|
| -
|
| -bool AXNodeObject::isNativeCheckboxOrRadio() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!isHTMLInputElement(node))
|
| - return false;
|
| -
|
| - HTMLInputElement* input = toHTMLInputElement(node);
|
| - return input->type() == InputTypeNames::checkbox || input->type() == InputTypeNames::radio;
|
| -}
|
| -
|
| -bool AXNodeObject::isNativeImage() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return false;
|
| -
|
| - if (isHTMLImageElement(*node))
|
| - return true;
|
| -
|
| - if (isHTMLPlugInElement(*node))
|
| - return true;
|
| -
|
| - if (isHTMLInputElement(*node))
|
| - return toHTMLInputElement(*node).type() == InputTypeNames::image;
|
| -
|
| - return false;
|
| -}
|
| -
|
| -bool AXNodeObject::isNativeTextControl() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return false;
|
| -
|
| - if (isHTMLTextAreaElement(*node))
|
| - return true;
|
| -
|
| - if (isHTMLInputElement(*node))
|
| - return toHTMLInputElement(node)->isTextField();
|
| -
|
| - return false;
|
| -}
|
| -
|
| -bool AXNodeObject::isNonNativeTextControl() const
|
| -{
|
| - if (isNativeTextControl())
|
| - return false;
|
| -
|
| - if (hasContentEditableAttributeSet())
|
| - return true;
|
| -
|
| - if (isARIATextControl())
|
| - return true;
|
| -
|
| - return false;
|
| -}
|
| -
|
| -bool AXNodeObject::isPasswordField() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!isHTMLInputElement(node))
|
| - return false;
|
| -
|
| - if (ariaRoleAttribute() != UnknownRole)
|
| - return false;
|
| -
|
| - return toHTMLInputElement(node)->type() == InputTypeNames::password;
|
| -}
|
| -
|
| -bool AXNodeObject::isProgressIndicator() const
|
| -{
|
| - return roleValue() == ProgressIndicatorRole;
|
| -}
|
| -
|
| -bool AXNodeObject::isSlider() const
|
| -{
|
| - return roleValue() == SliderRole;
|
| -}
|
| -
|
| -bool AXNodeObject::isChecked() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return false;
|
| -
|
| - // First test for native checkedness semantics
|
| - if (isHTMLInputElement(*node))
|
| - return toHTMLInputElement(*node).shouldAppearChecked();
|
| -
|
| - // Else, if this is an ARIA checkbox or radio OR ARIA role menuitemcheckbox
|
| - // or menuitemradio, respect the aria-checked attribute
|
| - AccessibilityRole ariaRole = ariaRoleAttribute();
|
| - if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole
|
| - || ariaRole == MenuItemCheckBoxRole || ariaRole == MenuItemRadioRole) {
|
| - if (equalIgnoringCase(getAttribute(aria_checkedAttr), "true"))
|
| - return true;
|
| - return false;
|
| - }
|
| -
|
| - // Otherwise it's not checked
|
| - return false;
|
| -}
|
| -
|
| -bool AXNodeObject::isClickable() const
|
| -{
|
| - if (node()) {
|
| - if (node()->isElementNode() && toElement(node())->isDisabledFormControl())
|
| - return false;
|
| -
|
| - // Note: we can't call node()->willRespondToMouseClickEvents() because that triggers a style recalc and can delete this.
|
| - if (node()->hasEventListeners(EventTypeNames::mouseup) || node()->hasEventListeners(EventTypeNames::mousedown) || node()->hasEventListeners(EventTypeNames::click) || node()->hasEventListeners(EventTypeNames::DOMActivate))
|
| - return true;
|
| - }
|
| -
|
| - return AXObject::isClickable();
|
| -}
|
| -
|
| -bool AXNodeObject::isEnabled() const
|
| -{
|
| - if (equalIgnoringCase(getAttribute(aria_disabledAttr), "true"))
|
| - return false;
|
| -
|
| - Node* node = this->node();
|
| - if (!node || !node->isElementNode())
|
| - return true;
|
| -
|
| - return !toElement(node)->isDisabledFormControl();
|
| -}
|
| -
|
| -AccessibilityExpanded AXNodeObject::isExpanded() const
|
| -{
|
| - const AtomicString& expanded = getAttribute(aria_expandedAttr);
|
| - if (equalIgnoringCase(expanded, "true"))
|
| - return ExpandedExpanded;
|
| - if (equalIgnoringCase(expanded, "false"))
|
| - return ExpandedCollapsed;
|
| -
|
| - return ExpandedUndefined;
|
| -}
|
| -
|
| -bool AXNodeObject::isIndeterminate() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!isHTMLInputElement(node))
|
| - return false;
|
| -
|
| - return toHTMLInputElement(node)->shouldAppearIndeterminate();
|
| -}
|
| -
|
| -bool AXNodeObject::isPressed() const
|
| -{
|
| - if (!isButton())
|
| - return false;
|
| -
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return false;
|
| -
|
| - // If this is an ARIA button, check the aria-pressed attribute rather than node()->active()
|
| - if (ariaRoleAttribute() == ButtonRole) {
|
| - if (equalIgnoringCase(getAttribute(aria_pressedAttr), "true"))
|
| - return true;
|
| - return false;
|
| - }
|
| -
|
| - return node->active();
|
| -}
|
| -
|
| -bool AXNodeObject::isReadOnly() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return true;
|
| -
|
| - if (isHTMLTextAreaElement(*node))
|
| - return toHTMLTextAreaElement(*node).isReadOnly();
|
| -
|
| - if (isHTMLInputElement(*node)) {
|
| - HTMLInputElement& input = toHTMLInputElement(*node);
|
| - if (input.isTextField())
|
| - return input.isReadOnly();
|
| - }
|
| -
|
| - return !node->hasEditableStyle();
|
| -}
|
| -
|
| -bool AXNodeObject::isRequired() const
|
| -{
|
| - if (equalIgnoringCase(getAttribute(aria_requiredAttr), "true"))
|
| - return true;
|
| -
|
| - Node* n = this->node();
|
| - if (n && (n->isElementNode() && toElement(n)->isFormControlElement()))
|
| - return toHTMLFormControlElement(n)->isRequired();
|
| -
|
| - return false;
|
| -}
|
| -
|
| -bool AXNodeObject::canSetFocusAttribute() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return false;
|
| -
|
| - if (isWebArea())
|
| - return true;
|
| -
|
| - // NOTE: It would be more accurate to ask the document whether setFocusedNode() would
|
| - // do anything. For example, setFocusedNode() will do nothing if the current focused
|
| - // node will not relinquish the focus.
|
| - if (!node)
|
| - return false;
|
| -
|
| - if (isDisabledFormControl(node))
|
| - return false;
|
| -
|
| - return node->isElementNode() && toElement(node)->supportsFocus();
|
| -}
|
| -
|
| -bool AXNodeObject::canSetValueAttribute() const
|
| -{
|
| - if (equalIgnoringCase(getAttribute(aria_readonlyAttr), "true"))
|
| - return false;
|
| -
|
| - 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();
|
| -}
|
| -
|
| -bool AXNodeObject::canvasHasFallbackContent() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!isHTMLCanvasElement(node))
|
| - return false;
|
| -
|
| - // If it has any children that are elements, we'll assume it might be fallback
|
| - // content. If it has no children or its only children are not elements
|
| - // (e.g. just text nodes), it doesn't have fallback content.
|
| - return ElementTraversal::firstChild(*node);
|
| -}
|
| -
|
| -bool AXNodeObject::exposesTitleUIElement() const
|
| -{
|
| - if (!isControl())
|
| - return false;
|
| -
|
| - // If this control is ignored (because it's invisible),
|
| - // then the label needs to be exposed so it can be visible to accessibility.
|
| - if (accessibilityIsIgnored())
|
| - return true;
|
| -
|
| - // ARIA: section 2A, bullet #3 says if aria-labeledby or aria-label appears, it should
|
| - // override the "label" element association.
|
| - bool hasTextAlternative = (!ariaLabeledByAttribute().isEmpty() || !getAttribute(aria_labelAttr).isEmpty());
|
| -
|
| - // Checkboxes and radio buttons use the text of their title ui element as their own AXTitle.
|
| - // This code controls whether the title ui element should appear in the AX tree (usually, no).
|
| - // It should appear if the control already has a label (which will be used as the AXTitle instead).
|
| - if (isCheckboxOrRadio())
|
| - return hasTextAlternative;
|
| -
|
| - // When controls have their own descriptions, the title element should be ignored.
|
| - if (hasTextAlternative)
|
| - return false;
|
| -
|
| - return true;
|
| -}
|
| -
|
| -int AXNodeObject::headingLevel() const
|
| -{
|
| - // headings can be in block flow and non-block flow
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return 0;
|
| -
|
| - if (ariaRoleAttribute() == HeadingRole)
|
| - return getAttribute(aria_levelAttr).toInt();
|
| -
|
| - if (!node->isHTMLElement())
|
| - return 0;
|
| -
|
| - HTMLElement& element = toHTMLElement(*node);
|
| - if (element.hasTagName(h1Tag))
|
| - return 1;
|
| -
|
| - if (element.hasTagName(h2Tag))
|
| - return 2;
|
| -
|
| - if (element.hasTagName(h3Tag))
|
| - return 3;
|
| -
|
| - if (element.hasTagName(h4Tag))
|
| - return 4;
|
| -
|
| - if (element.hasTagName(h5Tag))
|
| - return 5;
|
| -
|
| - if (element.hasTagName(h6Tag))
|
| - return 6;
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -unsigned AXNodeObject::hierarchicalLevel() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node || !node->isElementNode())
|
| - return 0;
|
| - Element* element = toElement(node);
|
| - String ariaLevel = element->getAttribute(aria_levelAttr);
|
| - if (!ariaLevel.isEmpty())
|
| - return ariaLevel.toInt();
|
| -
|
| - // Only tree item will calculate its level through the DOM currently.
|
| - if (roleValue() != TreeItemRole)
|
| - return 0;
|
| -
|
| - // Hierarchy leveling starts at 1, to match the aria-level spec.
|
| - // We measure tree hierarchy by the number of groups that the item is within.
|
| - unsigned level = 1;
|
| - for (AXObject* parent = parentObject(); parent; parent = parent->parentObject()) {
|
| - AccessibilityRole parentRole = parent->roleValue();
|
| - if (parentRole == GroupRole)
|
| - level++;
|
| - else if (parentRole == TreeRole)
|
| - break;
|
| - }
|
| -
|
| - return level;
|
| -}
|
| -
|
| -String AXNodeObject::text() const
|
| -{
|
| - // If this is a user defined static text, use the accessible name computation.
|
| - if (ariaRoleAttribute() == StaticTextRole)
|
| - return ariaAccessibilityDescription();
|
| -
|
| - if (!isTextControl())
|
| - return String();
|
| -
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return String();
|
| -
|
| - if (isNativeTextControl() && (isHTMLTextAreaElement(*node) || isHTMLInputElement(*node)))
|
| - return toHTMLTextFormControlElement(*node).value();
|
| -
|
| - if (!node->isElementNode())
|
| - return String();
|
| -
|
| - return toElement(node)->innerText();
|
| -}
|
| -
|
| -AXObject* AXNodeObject::titleUIElement() const
|
| -{
|
| - if (!node() || !node()->isElementNode())
|
| - return 0;
|
| -
|
| - if (isFieldset())
|
| - return axObjectCache()->getOrCreate(toHTMLFieldSetElement(node())->legend());
|
| -
|
| - HTMLLabelElement* label = labelForElement(toElement(node()));
|
| - if (label)
|
| - return axObjectCache()->getOrCreate(label);
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -AccessibilityButtonState AXNodeObject::checkboxOrRadioValue() const
|
| -{
|
| - if (isNativeCheckboxOrRadio())
|
| - return isChecked() ? ButtonStateOn : ButtonStateOff;
|
| -
|
| - return AXObject::checkboxOrRadioValue();
|
| -}
|
| -
|
| -void AXNodeObject::colorValue(int& r, int& g, int& b) const
|
| -{
|
| - r = 0;
|
| - g = 0;
|
| - b = 0;
|
| -
|
| - if (!isColorWell())
|
| - return;
|
| -
|
| - if (!isHTMLInputElement(node()))
|
| - return;
|
| -
|
| - HTMLInputElement* input = toHTMLInputElement(node());
|
| - const AtomicString& type = input->getAttribute(typeAttr);
|
| - if (!equalIgnoringCase(type, "color"))
|
| - return;
|
| -
|
| - // HTMLInputElement::value always returns a string parseable by Color.
|
| - Color color;
|
| - bool success = color.setFromString(input->value());
|
| - ASSERT_UNUSED(success, success);
|
| - r = color.red();
|
| - g = color.green();
|
| - b = color.blue();
|
| -}
|
| -
|
| -String AXNodeObject::valueDescription() const
|
| -{
|
| - if (!supportsRangeValue())
|
| - return String();
|
| -
|
| - return getAttribute(aria_valuetextAttr).string();
|
| -}
|
| -
|
| -float AXNodeObject::valueForRange() const
|
| -{
|
| - if (hasAttribute(aria_valuenowAttr))
|
| - return getAttribute(aria_valuenowAttr).toFloat();
|
| -
|
| - if (isHTMLInputElement(node())) {
|
| - HTMLInputElement& input = toHTMLInputElement(*node());
|
| - if (input.type() == InputTypeNames::range)
|
| - return input.valueAsNumber();
|
| - }
|
| -
|
| - return 0.0;
|
| -}
|
| -
|
| -float AXNodeObject::maxValueForRange() const
|
| -{
|
| - if (hasAttribute(aria_valuemaxAttr))
|
| - return getAttribute(aria_valuemaxAttr).toFloat();
|
| -
|
| - if (isHTMLInputElement(node())) {
|
| - HTMLInputElement& input = toHTMLInputElement(*node());
|
| - if (input.type() == InputTypeNames::range)
|
| - return input.maximum();
|
| - }
|
| -
|
| - return 0.0;
|
| -}
|
| -
|
| -float AXNodeObject::minValueForRange() const
|
| -{
|
| - if (hasAttribute(aria_valueminAttr))
|
| - return getAttribute(aria_valueminAttr).toFloat();
|
| -
|
| - if (isHTMLInputElement(node())) {
|
| - HTMLInputElement& input = toHTMLInputElement(*node());
|
| - if (input.type() == InputTypeNames::range)
|
| - return input.minimum();
|
| - }
|
| -
|
| - return 0.0;
|
| -}
|
| -
|
| -float AXNodeObject::stepValueForRange() const
|
| -{
|
| - return getAttribute(stepAttr).toFloat();
|
| -}
|
| -
|
| -String AXNodeObject::stringValue() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return String();
|
| -
|
| - if (ariaRoleAttribute() == StaticTextRole) {
|
| - String staticText = text();
|
| - if (!staticText.length())
|
| - staticText = textUnderElement();
|
| - return staticText;
|
| - }
|
| -
|
| - if (node->isTextNode())
|
| - return textUnderElement();
|
| -
|
| - if (isHTMLSelectElement(*node)) {
|
| - HTMLSelectElement& selectElement = toHTMLSelectElement(*node);
|
| - int selectedIndex = selectElement.selectedIndex();
|
| - const WillBeHeapVector<RawPtrWillBeMember<HTMLElement> >& listItems = selectElement.listItems();
|
| - if (selectedIndex >= 0 && static_cast<size_t>(selectedIndex) < listItems.size()) {
|
| - const AtomicString& overriddenDescription = listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
|
| - if (!overriddenDescription.isNull())
|
| - return overriddenDescription;
|
| - }
|
| - if (!selectElement.multiple())
|
| - return selectElement.value();
|
| - return String();
|
| - }
|
| -
|
| - if (isTextControl())
|
| - return text();
|
| -
|
| - // 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;
|
| - // this would require subclassing or making accessibilityAttributeNames do something other than return a
|
| - // single static array.
|
| - return String();
|
| -}
|
| -
|
| -
|
| -const AtomicString& AXNodeObject::textInputType() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!isHTMLInputElement(node))
|
| - return nullAtom;
|
| -
|
| - HTMLInputElement& input = toHTMLInputElement(*node);
|
| - if (input.isTextField())
|
| - return input.type();
|
| - return nullAtom;
|
| -}
|
| -
|
| -String AXNodeObject::ariaDescribedByAttribute() const
|
| -{
|
| - WillBeHeapVector<RawPtrWillBeMember<Element> > elements;
|
| - elementsFromAttribute(elements, aria_describedbyAttr);
|
| -
|
| - return accessibilityDescriptionForElements(elements);
|
| -}
|
| -
|
| -String AXNodeObject::ariaLabeledByAttribute() const
|
| -{
|
| - WillBeHeapVector<RawPtrWillBeMember<Element> > elements;
|
| - ariaLabeledByElements(elements);
|
| -
|
| - return accessibilityDescriptionForElements(elements);
|
| -}
|
| -
|
| -AccessibilityRole AXNodeObject::ariaRoleAttribute() const
|
| -{
|
| - return m_ariaRole;
|
| -}
|
| -
|
| -// When building the textUnderElement for an object, determine whether or not
|
| -// we should include the inner text of this given descendant object or skip it.
|
| -static bool shouldUseAccessibilityObjectInnerText(AXObject* obj)
|
| -{
|
| - // Consider this hypothetical example:
|
| - // <div tabindex=0>
|
| - // <h2>
|
| - // Table of contents
|
| - // </h2>
|
| - // <a href="#start">Jump to start of book</a>
|
| - // <ul>
|
| - // <li><a href="#1">Chapter 1</a></li>
|
| - // <li><a href="#1">Chapter 2</a></li>
|
| - // </ul>
|
| - // </div>
|
| - //
|
| - // The goal is to return a reasonable title for the outer container div, because
|
| - // it's focusable - but without making its title be the full inner text, which is
|
| - // quite long. As a heuristic, skip links, controls, and elements that are usually
|
| - // containers with lots of children.
|
| -
|
| - // Skip hidden children
|
| - if (obj->isInertOrAriaHidden())
|
| - return false;
|
| -
|
| - // Skip focusable children, so we don't include the text of links and controls.
|
| - if (obj->canSetFocusAttribute())
|
| - return false;
|
| -
|
| - // Skip big container elements like lists, tables, etc.
|
| - if (obj->isList() || obj->isAXTable() || obj->isTree() || obj->isCanvas())
|
| - return false;
|
| -
|
| - return true;
|
| -}
|
| -
|
| -String AXNodeObject::textUnderElement() const
|
| -{
|
| - Node* node = this->node();
|
| - if (node && node->isTextNode())
|
| - return toText(node)->wholeText();
|
| -
|
| - StringBuilder builder;
|
| - for (AXObject* child = firstChild(); child; child = child->nextSibling()) {
|
| - if (!shouldUseAccessibilityObjectInnerText(child))
|
| - continue;
|
| -
|
| - if (child->isAXNodeObject()) {
|
| - Vector<AccessibilityText> textOrder;
|
| - toAXNodeObject(child)->alternativeText(textOrder);
|
| - if (textOrder.size() > 0) {
|
| - builder.append(textOrder[0].text);
|
| - continue;
|
| - }
|
| - }
|
| -
|
| - builder.append(child->textUnderElement());
|
| - }
|
| -
|
| - return builder.toString();
|
| -}
|
| -
|
| -String AXNodeObject::accessibilityDescription() const
|
| -{
|
| - // Static text should not have a description, it should only have a stringValue.
|
| - if (roleValue() == StaticTextRole)
|
| - return String();
|
| -
|
| - String ariaDescription = ariaAccessibilityDescription();
|
| - if (!ariaDescription.isEmpty())
|
| - return ariaDescription;
|
| -
|
| - if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
|
| - // Images should use alt as long as the attribute is present, even if empty.
|
| - // Otherwise, it should fallback to other methods, like the title attribute.
|
| - const AtomicString& alt = getAttribute(altAttr);
|
| - if (!alt.isNull())
|
| - return alt;
|
| - }
|
| -
|
| - // An element's descriptive text is comprised of title() (what's visible on the screen) and accessibilityDescription() (other descriptive text).
|
| - // Both are used to generate what a screen reader speaks.
|
| - // If this point is reached (i.e. there's no accessibilityDescription) and there's no title(), we should fallback to using the title attribute.
|
| - // The title attribute is normally used as help text (because it is a tooltip), but if there is nothing else available, this should be used (according to ARIA).
|
| - if (title().isEmpty())
|
| - return getAttribute(titleAttr);
|
| -
|
| - return String();
|
| -}
|
| -
|
| -String AXNodeObject::title() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return String();
|
| -
|
| - bool isInputElement = isHTMLInputElement(*node);
|
| - if (isInputElement) {
|
| - HTMLInputElement& input = toHTMLInputElement(*node);
|
| - if (input.isTextButton())
|
| - return input.valueWithDefault();
|
| - }
|
| -
|
| - if (isInputElement || AXObject::isARIAInput(ariaRoleAttribute()) || isControl()) {
|
| - HTMLLabelElement* label = labelForElement(toElement(node));
|
| - if (label && !exposesTitleUIElement())
|
| - return label->innerText();
|
| - }
|
| -
|
| - // If this node isn't rendered, there's no inner text we can extract from a select element.
|
| - if (!isAXRenderObject() && isHTMLSelectElement(*node))
|
| - return String();
|
| -
|
| - switch (roleValue()) {
|
| - case PopUpButtonRole:
|
| - // Native popup buttons should not use their button children's text as a title. That value is retrieved through stringValue().
|
| - if (isHTMLSelectElement(*node))
|
| - return String();
|
| - case ButtonRole:
|
| - case ToggleButtonRole:
|
| - case CheckBoxRole:
|
| - case ListBoxOptionRole:
|
| - case MenuButtonRole:
|
| - case MenuItemRole:
|
| - case MenuItemCheckBoxRole:
|
| - case MenuItemRadioRole:
|
| - case RadioButtonRole:
|
| - case TabRole:
|
| - return textUnderElement();
|
| - // SVGRoots should not use the text under itself as a title. That could include the text of objects like <text>.
|
| - case SVGRootRole:
|
| - return String();
|
| - default:
|
| - break;
|
| - }
|
| -
|
| - if (isHeading() || isLink())
|
| - return textUnderElement();
|
| -
|
| - // If it's focusable but it's not content editable or a known control type, then it will appear to
|
| - // the user as a single atomic object, so we should use its text as the default title.
|
| - if (isGenericFocusableElement())
|
| - return textUnderElement();
|
| -
|
| - return String();
|
| -}
|
| -
|
| -String AXNodeObject::helpText() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return String();
|
| -
|
| - const AtomicString& ariaHelp = getAttribute(aria_helpAttr);
|
| - if (!ariaHelp.isEmpty())
|
| - return ariaHelp;
|
| -
|
| - String describedBy = ariaDescribedByAttribute();
|
| - if (!describedBy.isEmpty())
|
| - return describedBy;
|
| -
|
| - String description = accessibilityDescription();
|
| - for (Node* curr = node; curr; curr = curr->parentNode()) {
|
| - if (curr->isHTMLElement()) {
|
| - const AtomicString& summary = toElement(curr)->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)->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.
|
| - AXObject* axObj = axObjectCache()->getOrCreate(curr);
|
| - if (axObj) {
|
| - AccessibilityRole role = axObj->roleValue();
|
| - if (role != GroupRole && role != UnknownRole)
|
| - break;
|
| - }
|
| - }
|
| -
|
| - return String();
|
| -}
|
| -
|
| -LayoutRect AXNodeObject::elementRect() const
|
| -{
|
| - // First check if it has a custom rect, for example if this element is tied to a canvas path.
|
| - if (!m_explicitElementRect.isEmpty())
|
| - return m_explicitElementRect;
|
| -
|
| - // FIXME: If there are a lot of elements in the canvas, it will be inefficient.
|
| - // We can avoid the inefficient calculations by using AXComputedObjectAttributeCache.
|
| - if (node()->parentElement()->isInCanvasSubtree()) {
|
| - LayoutRect rect;
|
| -
|
| - for (Node* child = node()->firstChild(); child; child = child->nextSibling()) {
|
| - if (child->isHTMLElement()) {
|
| - if (AXObject* obj = axObjectCache()->get(child)) {
|
| - if (rect.isEmpty())
|
| - rect = obj->elementRect();
|
| - else
|
| - rect.unite(obj->elementRect());
|
| - }
|
| - }
|
| - }
|
| -
|
| - if (!rect.isEmpty())
|
| - return rect;
|
| - }
|
| -
|
| - // If this object doesn't have an explicit element rect or computable from its children,
|
| - // 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 (AXObject* positionProvider = parentObject(); positionProvider; positionProvider = positionProvider->parentObject()) {
|
| - if (positionProvider->isAXRenderObject()) {
|
| - LayoutRect parentRect = positionProvider->elementRect();
|
| - boundingBox.setSize(LayoutSize(parentRect.width(), LayoutUnit(std::min(10.0f, parentRect.height().toFloat()))));
|
| - boundingBox.setLocation(parentRect.location());
|
| - break;
|
| - }
|
| - }
|
| -
|
| - return boundingBox;
|
| -}
|
| -
|
| -AXObject* AXNodeObject::computeParent() const
|
| -{
|
| - if (!node())
|
| - return 0;
|
| -
|
| - Node* parentObj = node()->parentNode();
|
| - if (parentObj)
|
| - return axObjectCache()->getOrCreate(parentObj);
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -AXObject* AXNodeObject::computeParentIfExists() const
|
| -{
|
| - if (!node())
|
| - return 0;
|
| -
|
| - Node* parentObj = node()->parentNode();
|
| - if (parentObj)
|
| - return axObjectCache()->get(parentObj);
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -AXObject* AXNodeObject::firstChild() const
|
| -{
|
| - if (!node())
|
| - return 0;
|
| -
|
| - Node* firstChild = node()->firstChild();
|
| -
|
| - if (!firstChild)
|
| - return 0;
|
| -
|
| - return axObjectCache()->getOrCreate(firstChild);
|
| -}
|
| -
|
| -AXObject* AXNodeObject::nextSibling() const
|
| -{
|
| - if (!node())
|
| - return 0;
|
| -
|
| - Node* nextSibling = node()->nextSibling();
|
| - if (!nextSibling)
|
| - return 0;
|
| -
|
| - return axObjectCache()->getOrCreate(nextSibling);
|
| -}
|
| -
|
| -void AXNodeObject::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() && !isHTMLCanvasElement(*m_node))
|
| - return;
|
| -
|
| - for (Node* child = m_node->firstChild(); child; child = child->nextSibling())
|
| - addChild(axObjectCache()->getOrCreate(child));
|
| -
|
| - for (unsigned i = 0; i < m_children.size(); ++i)
|
| - m_children[i].get()->setParent(this);
|
| -}
|
| -
|
| -void AXNodeObject::addChild(AXObject* child)
|
| -{
|
| - insertChild(child, m_children.size());
|
| -}
|
| -
|
| -void AXNodeObject::insertChild(AXObject* 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()) {
|
| - AccessibilityChildrenVector 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);
|
| - }
|
| -}
|
| -
|
| -bool AXNodeObject::canHaveChildren() const
|
| -{
|
| - // If this is an AXRenderObject, 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() && !isAXRenderObject())
|
| - return false;
|
| -
|
| - // Elements that should not have children
|
| - switch (roleValue()) {
|
| - case ImageRole:
|
| - case ButtonRole:
|
| - case PopUpButtonRole:
|
| - case CheckBoxRole:
|
| - case RadioButtonRole:
|
| - case TabRole:
|
| - case ToggleButtonRole:
|
| - case ListBoxOptionRole:
|
| - case ScrollBarRole:
|
| - return false;
|
| - case StaticTextRole:
|
| - if (!axObjectCache()->inlineTextBoxAccessibilityEnabled())
|
| - return false;
|
| - default:
|
| - return true;
|
| - }
|
| -}
|
| -
|
| -Element* AXNodeObject::actionElement() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return 0;
|
| -
|
| - if (isHTMLInputElement(*node)) {
|
| - HTMLInputElement& input = toHTMLInputElement(*node);
|
| - if (!input.isDisabledFormControl() && (isCheckboxOrRadio() || input.isTextButton()))
|
| - return &input;
|
| - } else if (isHTMLButtonElement(*node)) {
|
| - return toElement(node);
|
| - }
|
| -
|
| - if (isFileUploadButton())
|
| - return toElement(node);
|
| -
|
| - if (AXObject::isARIAInput(ariaRoleAttribute()))
|
| - return toElement(node);
|
| -
|
| - if (isImageButton())
|
| - return toElement(node);
|
| -
|
| - if (isHTMLSelectElement(*node))
|
| - return toElement(node);
|
| -
|
| - switch (roleValue()) {
|
| - case ButtonRole:
|
| - case PopUpButtonRole:
|
| - case ToggleButtonRole:
|
| - case TabRole:
|
| - case MenuItemRole:
|
| - case MenuItemCheckBoxRole:
|
| - case MenuItemRadioRole:
|
| - case ListItemRole:
|
| - return toElement(node);
|
| - default:
|
| - break;
|
| - }
|
| -
|
| - Element* elt = anchorElement();
|
| - if (!elt)
|
| - elt = mouseButtonListener();
|
| - return elt;
|
| -}
|
| -
|
| -Element* AXNodeObject::anchorElement() const
|
| -{
|
| - Node* node = this->node();
|
| - if (!node)
|
| - return 0;
|
| -
|
| - AXObjectCacheImpl* cache = axObjectCache();
|
| -
|
| - // search up the DOM tree for an anchor element
|
| - // NOTE: this assumes that any non-image with an anchor is an HTMLAnchorElement
|
| - for ( ; node; node = node->parentNode()) {
|
| - if (isHTMLAnchorElement(*node) || (node->renderer() && cache->getOrCreate(node->renderer())->isAnchor()))
|
| - return toElement(node);
|
| - }
|
| -
|
| - return 0;
|
| -}
|
| -
|
| -Document* AXNodeObject::document() const
|
| -{
|
| - if (!node())
|
| - return 0;
|
| - return &node()->document();
|
| -}
|
| -
|
| -void AXNodeObject::setNode(Node* node)
|
| -{
|
| - m_node = node;
|
| -}
|
| -
|
| -AXObject* AXNodeObject::correspondingControlForLabelElement() const
|
| -{
|
| - HTMLLabelElement* labelElement = labelElementContainer();
|
| - if (!labelElement)
|
| - return 0;
|
| -
|
| - HTMLElement* correspondingControl = labelElement->control();
|
| - if (!correspondingControl)
|
| - return 0;
|
| -
|
| - // 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 axObjectCache()->getOrCreate(correspondingControl);
|
| -}
|
| -
|
| -HTMLLabelElement* AXNodeObject::labelElementContainer() const
|
| -{
|
| - if (!node())
|
| - return 0;
|
| -
|
| - // the control element should not be considered part of the label
|
| - if (isControl())
|
| - return 0;
|
| -
|
| - // find if this has a ancestor that is a label
|
| - return Traversal<HTMLLabelElement>::firstAncestorOrSelf(*node());
|
| -}
|
| -
|
| -void AXNodeObject::setFocused(bool on)
|
| -{
|
| - if (!canSetFocusAttribute())
|
| - return;
|
| -
|
| - Document* document = this->document();
|
| - if (!on) {
|
| - document->setFocusedElement(nullptr);
|
| - } else {
|
| - Node* node = this->node();
|
| - if (node && node->isElementNode()) {
|
| - // 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(nullptr);
|
| -
|
| - toElement(node)->focus();
|
| - } else {
|
| - document->setFocusedElement(nullptr);
|
| - }
|
| - }
|
| -}
|
| -
|
| -void AXNodeObject::increment()
|
| -{
|
| - UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
|
| - alterSliderValue(true);
|
| -}
|
| -
|
| -void AXNodeObject::decrement()
|
| -{
|
| - UserGestureIndicator gestureIndicator(DefinitelyProcessingNewUserGesture);
|
| - alterSliderValue(false);
|
| -}
|
| -
|
| -void AXNodeObject::childrenChanged()
|
| -{
|
| - // This method is meant as a quick way of marking a portion of the accessibility tree dirty.
|
| - if (!node() && !renderer())
|
| - return;
|
| -
|
| - axObjectCache()->postNotification(this, document(), AXObjectCacheImpl::AXChildrenChanged, true);
|
| -
|
| - // 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 (AXObject* 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.
|
| - if (parent->isLiveRegion())
|
| - axObjectCache()->postNotification(parent, parent->document(), AXObjectCacheImpl::AXLiveRegionChanged, true);
|
| -
|
| - // If this element is an ARIA text box or content editable, post a "value changed" notification on it
|
| - // so that it behaves just like a native input element or textarea.
|
| - if (isNonNativeTextControl())
|
| - axObjectCache()->postNotification(parent, parent->document(), AXObjectCacheImpl::AXValueChanged, true);
|
| - }
|
| -}
|
| -
|
| -void AXNodeObject::selectionChanged()
|
| -{
|
| - // Post the selected text changed event on the first ancestor that's
|
| - // focused (to handle form controls, ARIA text boxes and contentEditable),
|
| - // or the web area if the selection is just in the document somewhere.
|
| - if (isFocused() || isWebArea())
|
| - axObjectCache()->postNotification(this, document(), AXObjectCacheImpl::AXSelectedTextChanged, true);
|
| - else
|
| - AXObject::selectionChanged(); // Calls selectionChanged on parent.
|
| -}
|
| -
|
| -void AXNodeObject::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.
|
| - AXObjectCacheImpl* cache = axObjectCache();
|
| - for (Node* parentNode = node(); parentNode; parentNode = parentNode->parentNode()) {
|
| - AXObject* parent = cache->get(parentNode);
|
| - if (!parent)
|
| - continue;
|
| -
|
| - if (parent->isLiveRegion())
|
| - cache->postNotification(parentNode, AXObjectCacheImpl::AXLiveRegionChanged, true);
|
| -
|
| - // If this element is an ARIA text box or content editable, post a "value changed" notification on it
|
| - // so that it behaves just like a native input element or textarea.
|
| - if (parent->isNonNativeTextControl())
|
| - cache->postNotification(parentNode, AXObjectCacheImpl::AXValueChanged, true);
|
| - }
|
| -}
|
| -
|
| -void AXNodeObject::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();
|
| -}
|
| -
|
| -String AXNodeObject::alternativeTextForWebArea() const
|
| -{
|
| - // The WebArea description should follow this order:
|
| - // aria-label on the <html>
|
| - // title on the <html>
|
| - // <title> inside the <head> (of it was set through JS)
|
| - // name on the <html>
|
| - // For iframes:
|
| - // aria-label on the <iframe>
|
| - // title on the <iframe>
|
| - // name on the <iframe>
|
| -
|
| - Document* document = this->document();
|
| - if (!document)
|
| - return String();
|
| -
|
| - // Check if the HTML element has an aria-label for the webpage.
|
| - if (Element* documentElement = document->documentElement()) {
|
| - const AtomicString& ariaLabel = documentElement->getAttribute(aria_labelAttr);
|
| - if (!ariaLabel.isEmpty())
|
| - return ariaLabel;
|
| - }
|
| -
|
| - if (HTMLFrameOwnerElement* owner = document->ownerElement()) {
|
| - if (isHTMLFrameElementBase(*owner)) {
|
| - const AtomicString& title = owner->getAttribute(titleAttr);
|
| - if (!title.isEmpty())
|
| - return title;
|
| - }
|
| - return owner->getNameAttribute();
|
| - }
|
| -
|
| - String documentTitle = document->title();
|
| - if (!documentTitle.isEmpty())
|
| - return documentTitle;
|
| -
|
| - if (HTMLElement* body = document->body())
|
| - return body->getNameAttribute();
|
| -
|
| - return String();
|
| -}
|
| -
|
| -void AXNodeObject::alternativeText(Vector<AccessibilityText>& textOrder) const
|
| -{
|
| - if (isWebArea()) {
|
| - String webAreaText = alternativeTextForWebArea();
|
| - if (!webAreaText.isEmpty())
|
| - textOrder.append(AccessibilityText(webAreaText, AlternativeText));
|
| - return;
|
| - }
|
| -
|
| - ariaLabeledByText(textOrder);
|
| -
|
| - const AtomicString& ariaLabel = getAttribute(aria_labelAttr);
|
| - if (!ariaLabel.isEmpty())
|
| - textOrder.append(AccessibilityText(ariaLabel, AlternativeText));
|
| -
|
| - if (isImage() || isInputImage() || isNativeImage() || isCanvas()) {
|
| - // Images should use alt as long as the attribute is present, even if empty.
|
| - // Otherwise, it should fallback to other methods, like the title attribute.
|
| - const AtomicString& alt = getAttribute(altAttr);
|
| - if (!alt.isNull())
|
| - textOrder.append(AccessibilityText(alt, AlternativeText));
|
| - }
|
| -}
|
| -
|
| -void AXNodeObject::ariaLabeledByText(Vector<AccessibilityText>& textOrder) const
|
| -{
|
| - String ariaLabeledBy = ariaLabeledByAttribute();
|
| - if (!ariaLabeledBy.isEmpty()) {
|
| - WillBeHeapVector<RawPtrWillBeMember<Element> > elements;
|
| - ariaLabeledByElements(elements);
|
| -
|
| - unsigned length = elements.size();
|
| - for (unsigned k = 0; k < length; k++) {
|
| - RefPtr<AXObject> axElement = axObjectCache()->getOrCreate(elements[k]);
|
| - textOrder.append(AccessibilityText(ariaLabeledBy, AlternativeText, axElement));
|
| - }
|
| - }
|
| -}
|
| -
|
| -void AXNodeObject::changeValueByPercent(float percentChange)
|
| -{
|
| - float range = maxValueForRange() - minValueForRange();
|
| - float value = valueForRange();
|
| -
|
| - value += range * (percentChange / 100);
|
| - setValue(String::number(value));
|
| -
|
| - axObjectCache()->postNotification(node(), AXObjectCacheImpl::AXValueChanged, true);
|
| -}
|
| -
|
| -} // namespace blink
|
|
|