Index: third_party/WebKit/WebCore/page/AccessibilityRenderObject.cpp |
=================================================================== |
--- third_party/WebKit/WebCore/page/AccessibilityRenderObject.cpp (revision 9383) |
+++ third_party/WebKit/WebCore/page/AccessibilityRenderObject.cpp (working copy) |
@@ -1,2467 +1,2467 @@ |
-/* |
-* Copyright (C) 2008 Apple 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 "AccessibilityRenderObject.h" |
- |
-#include "AXObjectCache.h" |
-#include "AccessibilityListBox.h" |
-#include "AccessibilityImageMapLink.h" |
-#include "CharacterNames.h" |
-#include "EventNames.h" |
-#include "FloatRect.h" |
-#include "FocusController.h" |
-#include "Frame.h" |
-#include "FrameLoader.h" |
-#include "HTMLAreaElement.h" |
-#include "HTMLFrameElementBase.h" |
-#include "HTMLImageElement.h" |
-#include "HTMLInputElement.h" |
-#include "HTMLLabelElement.h" |
-#include "HTMLMapElement.h" |
-#include "HTMLOptGroupElement.h" |
-#include "HTMLOptionElement.h" |
-#include "HTMLOptionsCollection.h" |
-#include "HTMLSelectElement.h" |
-#include "HTMLTextAreaElement.h" |
-#include "HitTestRequest.h" |
-#include "HitTestResult.h" |
-#include "LocalizedStrings.h" |
-#include "NodeList.h" |
-#include "NotImplemented.h" |
-#include "Page.h" |
-#include "RenderFieldset.h" |
-#include "RenderFileUploadControl.h" |
-#include "RenderImage.h" |
-#include "RenderInline.h" |
-#include "RenderListBox.h" |
-#include "RenderListMarker.h" |
-#include "RenderMenuList.h" |
-#include "RenderText.h" |
-#include "RenderTextControl.h" |
-#include "RenderTheme.h" |
-#include "RenderView.h" |
-#include "RenderWidget.h" |
-#include "SelectionController.h" |
-#include "Text.h" |
-#include "TextIterator.h" |
-#include "htmlediting.h" |
-#include "visible_units.h" |
-#include <wtf/StdLibExtras.h> |
- |
-using namespace std; |
- |
-namespace WebCore { |
- |
-using namespace HTMLNames; |
- |
-AccessibilityRenderObject::AccessibilityRenderObject(RenderObject* renderer) |
- : m_renderer(renderer) |
- , m_ariaRole(UnknownRole) |
-{ |
- setAriaRole(); |
-#ifndef NDEBUG |
- m_renderer->setHasAXObject(true); |
-#endif |
-} |
- |
-AccessibilityRenderObject::~AccessibilityRenderObject() |
-{ |
- ASSERT(isDetached()); |
-} |
- |
-PassRefPtr<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) |
-{ |
- return adoptRef(new AccessibilityRenderObject(renderer)); |
-} |
- |
-void AccessibilityRenderObject::detach() |
-{ |
- clearChildren(); |
- AccessibilityObject::detach(); |
- |
-#ifndef NDEBUG |
- if (m_renderer) |
- m_renderer->setHasAXObject(false); |
-#endif |
- m_renderer = 0; |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::firstChild() const |
-{ |
- if (!m_renderer) |
- return 0; |
- |
- RenderObject* firstChild = m_renderer->firstChild(); |
- if (!firstChild) |
- return 0; |
- |
- return m_renderer->document()->axObjectCache()->get(firstChild); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::lastChild() const |
-{ |
- if (!m_renderer) |
- return 0; |
- |
- RenderObject* lastChild = m_renderer->lastChild(); |
- if (!lastChild) |
- return 0; |
- |
- return m_renderer->document()->axObjectCache()->get(lastChild); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::previousSibling() const |
-{ |
- if (!m_renderer) |
- return 0; |
- |
- RenderObject* previousSibling = m_renderer->previousSibling(); |
- if (!previousSibling) |
- return 0; |
- |
- return m_renderer->document()->axObjectCache()->get(previousSibling); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::nextSibling() const |
-{ |
- if (!m_renderer) |
- return 0; |
- |
- RenderObject* nextSibling = m_renderer->nextSibling(); |
- if (!nextSibling) |
- return 0; |
- |
- return m_renderer->document()->axObjectCache()->get(nextSibling); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::parentObject() const |
-{ |
- if (!m_renderer) |
- return 0; |
- |
- RenderObject *parent = m_renderer->parent(); |
- if (!parent) |
- return 0; |
- |
- if (ariaRoleAttribute() == MenuBarRole) |
- return m_renderer->document()->axObjectCache()->get(parent); |
- |
- // menuButton and its corresponding menu are DOM siblings, but Accessibility needs them to be parent/child |
- if (ariaRoleAttribute() == MenuRole) { |
- AccessibilityObject* parent = menuButtonForMenu(); |
- if (parent) |
- return parent; |
- } |
- |
- return m_renderer->document()->axObjectCache()->get(parent); |
-} |
- |
-bool AccessibilityRenderObject::isWebArea() const |
-{ |
- return roleValue() == WebAreaRole; |
-} |
- |
-bool AccessibilityRenderObject::isImageButton() const |
-{ |
- return isNativeImage() && roleValue() == ButtonRole; |
-} |
- |
-bool AccessibilityRenderObject::isAnchor() const |
-{ |
- return !isNativeImage() && isLink(); |
-} |
- |
-bool AccessibilityRenderObject::isNativeTextControl() const |
-{ |
- return m_renderer->isTextField() || m_renderer->isTextArea(); |
-} |
- |
-bool AccessibilityRenderObject::isTextControl() const |
-{ |
- AccessibilityRole role = roleValue(); |
- return role == TextAreaRole || role == TextFieldRole; |
-} |
- |
-bool AccessibilityRenderObject::isNativeImage() const |
-{ |
- return m_renderer->isImage(); |
-} |
- |
-bool AccessibilityRenderObject::isImage() const |
-{ |
- return roleValue() == ImageRole; |
-} |
- |
-bool AccessibilityRenderObject::isAttachment() const |
-{ |
- // Widgets are the replaced elements that we represent to AX as attachments |
- bool isWidget = m_renderer && m_renderer->isWidget(); |
- ASSERT(!isWidget || (m_renderer->isReplaced() && !isImage())); |
- return isWidget && ariaRoleAttribute() == UnknownRole; |
-} |
- |
-bool AccessibilityRenderObject::isPasswordField() const |
-{ |
- ASSERT(m_renderer); |
- if (!m_renderer->element() || !m_renderer->element()->isHTMLElement()) |
- return false; |
- if (ariaRoleAttribute() != UnknownRole) |
- return false; |
- |
- InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->element())); |
- if (!inputElement) |
- return false; |
- |
- return inputElement->isPasswordField(); |
-} |
- |
-bool AccessibilityRenderObject::isCheckboxOrRadio() const |
-{ |
- AccessibilityRole role = roleValue(); |
- return role == RadioButtonRole || role == CheckBoxRole; |
-} |
- |
-bool AccessibilityRenderObject::isFileUploadButton() const |
-{ |
- if (m_renderer && m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) { |
- HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); |
- return input->inputType() == HTMLInputElement::FILE; |
- } |
- |
- return false; |
-} |
- |
-bool AccessibilityRenderObject::isInputImage() const |
-{ |
- if (m_renderer && m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) { |
- HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); |
- return input->inputType() == HTMLInputElement::IMAGE; |
- } |
- |
- return false; |
-} |
- |
-bool AccessibilityRenderObject::isProgressIndicator() const |
-{ |
- return roleValue() == ProgressIndicatorRole; |
-} |
- |
-bool AccessibilityRenderObject::isSlider() const |
-{ |
- return roleValue() == SliderRole; |
-} |
- |
-bool AccessibilityRenderObject::isMenuRelated() const |
-{ |
- AccessibilityRole role = roleValue(); |
- return role == MenuRole || |
- role == MenuBarRole || |
- role == MenuButtonRole || |
- role == MenuItemRole; |
-} |
- |
-bool AccessibilityRenderObject::isMenu() const |
-{ |
- return roleValue() == MenuRole; |
-} |
- |
-bool AccessibilityRenderObject::isMenuBar() const |
-{ |
- return roleValue() == MenuBarRole; |
-} |
- |
-bool AccessibilityRenderObject::isMenuButton() const |
-{ |
- return roleValue() == MenuButtonRole; |
-} |
- |
-bool AccessibilityRenderObject::isMenuItem() const |
-{ |
- return roleValue() == MenuItemRole; |
-} |
- |
-bool AccessibilityRenderObject::isPressed() const |
-{ |
- ASSERT(m_renderer); |
- if (roleValue() != ButtonRole) |
- return false; |
- |
- Node* node = m_renderer->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).string(), "true")) |
- return true; |
- return false; |
- } |
- |
- return node->active(); |
-} |
- |
-bool AccessibilityRenderObject::isIndeterminate() const |
-{ |
- ASSERT(m_renderer); |
- if (!m_renderer->node() || !m_renderer->node()->isElementNode()) |
- return false; |
- |
- InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node())); |
- if (!inputElement) |
- return false; |
- |
- return inputElement->isIndeterminate(); |
-} |
- |
-bool AccessibilityRenderObject::isChecked() const |
-{ |
- ASSERT(m_renderer); |
- if (!m_renderer->node() || !m_renderer->node()->isElementNode()) |
- return false; |
- |
- InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node())); |
- if (!inputElement) |
- return false; |
- |
- return inputElement->isChecked(); |
-} |
- |
-bool AccessibilityRenderObject::isHovered() const |
-{ |
- ASSERT(m_renderer); |
- return m_renderer->node() && m_renderer->node()->hovered(); |
-} |
- |
-bool AccessibilityRenderObject::isMultiSelect() const |
-{ |
- ASSERT(m_renderer); |
- if (!m_renderer->isListBox()) |
- return false; |
- return m_renderer->element() && static_cast<HTMLSelectElement*>(m_renderer->element())->multiple(); |
-} |
- |
-bool AccessibilityRenderObject::isReadOnly() const |
-{ |
- ASSERT(m_renderer); |
- |
- if (isWebArea()) { |
- Document* document = m_renderer->document(); |
- if (!document) |
- return true; |
- |
- HTMLElement* body = document->body(); |
- if (body && body->isContentEditable()) |
- return false; |
- |
- Frame* frame = document->frame(); |
- if (!frame) |
- return true; |
- |
- return !frame->isContentEditable(); |
- } |
- |
- return !m_renderer->node() || !m_renderer->node()->isContentEditable(); |
-} |
- |
-bool AccessibilityRenderObject::isOffScreen() const |
-{ |
- ASSERT(m_renderer); |
- IntRect contentRect = m_renderer->absoluteClippedOverflowRect(); |
- FrameView* view = m_renderer->document()->frame()->view(); |
- FloatRect viewRect = view->visibleContentRect(); |
- viewRect.intersect(contentRect); |
- return viewRect.isEmpty(); |
-} |
- |
-int AccessibilityRenderObject::headingLevel(Node* node) |
-{ |
- // headings can be in block flow and non-block flow |
- if (!node) |
- return 0; |
- |
- if (RenderObject* renderer = node->renderer()) { |
- AccessibilityObject* axObjectForNode = node->document()->axObjectCache()->get(renderer); |
- if (axObjectForNode->ariaRoleAttribute() == HeadingRole) { |
- if (!node->isElementNode()) |
- return 0; |
- Element* element = static_cast<Element*>(node); |
- return element->getAttribute(aria_levelAttr).toInt(); |
- } |
- } |
- |
- |
- if (node->hasTagName(h1Tag)) |
- return 1; |
- |
- if (node->hasTagName(h2Tag)) |
- return 2; |
- |
- if (node->hasTagName(h3Tag)) |
- return 3; |
- |
- if (node->hasTagName(h4Tag)) |
- return 4; |
- |
- if (node->hasTagName(h5Tag)) |
- return 5; |
- |
- if (node->hasTagName(h6Tag)) |
- return 6; |
- |
- return 0; |
-} |
- |
-bool AccessibilityRenderObject::isHeading() const |
-{ |
- return roleValue() == HeadingRole; |
-} |
- |
-bool AccessibilityRenderObject::isLink() const |
-{ |
- return roleValue() == WebCoreLinkRole; |
-} |
- |
-bool AccessibilityRenderObject::isControl() const |
-{ |
- if (!m_renderer) |
- return false; |
- |
- Node* node = m_renderer->element(); |
- return node && ((node->isElementNode() && static_cast<Element*>(node)->isFormControlElement()) |
- || AccessibilityObject::isARIAControl(ariaRoleAttribute())); |
-} |
- |
-bool AccessibilityRenderObject::isFieldset() const |
-{ |
- if (!m_renderer) |
- return false; |
- |
- return m_renderer->isFieldset(); |
-} |
- |
-bool AccessibilityRenderObject::isGroup() const |
-{ |
- return roleValue() == GroupRole; |
-} |
- |
-const AtomicString& AccessibilityRenderObject::getAttribute(const QualifiedName& attribute) const |
-{ |
- Node* node = m_renderer->element(); |
- if (!node) |
- return nullAtom; |
- |
- if (!node->isElementNode()) |
- return nullAtom; |
- |
- Element* element = static_cast<Element*>(node); |
- return element->getAttribute(attribute); |
-} |
- |
-Element* AccessibilityRenderObject::anchorElement() const |
-{ |
- if (!m_renderer) |
- return 0; |
- |
- AXObjectCache* cache = axObjectCache(); |
- RenderObject* currRenderer; |
- |
- // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though. |
- for (currRenderer = m_renderer; currRenderer && !currRenderer->element(); currRenderer = currRenderer->parent()) { |
- if (currRenderer->isRenderBlock()) { |
- RenderInline* continuation = toRenderBlock(currRenderer)->inlineContinuation(); |
- if (continuation) |
- return cache->get(continuation)->anchorElement(); |
- } |
- } |
- |
- // bail if none found |
- if (!currRenderer) |
- return 0; |
- |
- // 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 (node->hasTagName(aTag) || (node->renderer() && cache->get(node->renderer())->isAnchor())) |
- return static_cast<Element*>(node); |
- } |
- |
- return 0; |
-} |
- |
-Element* AccessibilityRenderObject::actionElement() const |
-{ |
- if (!m_renderer) |
- return 0; |
- |
- Node* node = m_renderer->element(); |
- if (node) { |
- if (node->hasTagName(inputTag)) { |
- HTMLInputElement* input = static_cast<HTMLInputElement*>(node); |
- if (!input->disabled() && (isCheckboxOrRadio() || input->isTextButton())) |
- return input; |
- } else if (node->hasTagName(buttonTag)) |
- return static_cast<Element*>(node); |
- } |
- |
- if (isFileUploadButton()) |
- return static_cast<Element*>(m_renderer->element()); |
- |
- if (AccessibilityObject::isARIAInput(ariaRoleAttribute())) |
- return static_cast<Element*>(m_renderer->element()); |
- |
- if (isImageButton()) |
- return static_cast<Element*>(m_renderer->element()); |
- |
- if (m_renderer->isMenuList()) |
- return static_cast<RenderMenuList*>(m_renderer)->selectElement(); |
- |
- Element* elt = anchorElement(); |
- if (!elt) |
- elt = mouseButtonListener(); |
- return elt; |
-} |
- |
-Element* AccessibilityRenderObject::mouseButtonListener() const |
-{ |
- Node* node = m_renderer->element(); |
- if (!node) |
- return 0; |
- if (!node->isElementNode()) |
- return 0; |
- |
- // FIXME: Do the continuation search like anchorElement does |
- for (Element* element = static_cast<Element*>(node); element; element = element->parentElement()) { |
- if (element->inlineEventListenerForType(eventNames().clickEvent) || element->inlineEventListenerForType(eventNames().mousedownEvent) || element->inlineEventListenerForType(eventNames().mouseupEvent)) |
- return element; |
- } |
- |
- return 0; |
-} |
- |
-static Element* siblingWithAriaRole(String role, Node* node) |
-{ |
- Node* sibling = node->parent()->firstChild(); |
- while (sibling) { |
- if (sibling->isElementNode()) { |
- String siblingAriaRole = static_cast<Element*>(sibling)->getAttribute(roleAttr).string(); |
- if (equalIgnoringCase(siblingAriaRole, role)) |
- return static_cast<Element*>(sibling); |
- } |
- sibling = sibling->nextSibling(); |
- } |
- |
- return 0; |
-} |
- |
-Element* AccessibilityRenderObject::menuElementForMenuButton() const |
-{ |
- if (ariaRoleAttribute() != MenuButtonRole) |
- return 0; |
- |
- return siblingWithAriaRole("menu", renderer()->node()); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::menuForMenuButton() const |
-{ |
- Element* menu = menuElementForMenuButton(); |
- if (menu && menu->renderer()) |
- return m_renderer->document()->axObjectCache()->get(menu->renderer()); |
- return 0; |
-} |
- |
-Element* AccessibilityRenderObject::menuItemElementForMenu() const |
-{ |
- if (ariaRoleAttribute() != MenuRole) |
- return 0; |
- |
- return siblingWithAriaRole("menuitem", renderer()->node()); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::menuButtonForMenu() const |
-{ |
- Element* menuItem = menuItemElementForMenu(); |
- |
- if (menuItem && menuItem->renderer()) { |
- // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem |
- AccessibilityObject* menuItemAX = m_renderer->document()->axObjectCache()->get(menuItem->renderer()); |
- if (menuItemAX->isMenuButton()) |
- return menuItemAX; |
- } |
- return 0; |
-} |
- |
-String AccessibilityRenderObject::helpText() const |
-{ |
- if (!m_renderer) |
- return String(); |
- |
- for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { |
- if (curr->element() && curr->element()->isHTMLElement()) { |
- const AtomicString& summary = static_cast<Element*>(curr->element())->getAttribute(summaryAttr); |
- if (!summary.isEmpty()) |
- return summary; |
- const AtomicString& title = static_cast<Element*>(curr->element())->getAttribute(titleAttr); |
- if (!title.isEmpty()) |
- return title; |
- } |
- } |
- |
- return String(); |
-} |
- |
-String AccessibilityRenderObject::textUnderElement() const |
-{ |
- if (!m_renderer) |
- return String(); |
- |
- if (isFileUploadButton()) { |
- RenderFileUploadControl* uploadControl = static_cast<RenderFileUploadControl*>(m_renderer); |
- return uploadControl->buttonValue(); |
- } |
- |
- Node* node = m_renderer->element(); |
- if (node) { |
- if (Frame* frame = node->document()->frame()) { |
- // catch stale WebCoreAXObject (see <rdar://problem/3960196>) |
- if (frame->document() != node->document()) |
- return String(); |
- return plainText(rangeOfContents(node).get()); |
- } |
- } |
- |
- // return the null string for anonymous text because it is non-trivial to get |
- // the actual text and, so far, that is not needed |
- return String(); |
-} |
- |
-bool AccessibilityRenderObject::hasIntValue() const |
-{ |
- if (isHeading()) |
- return true; |
- |
- if (m_renderer->element() && isCheckboxOrRadio()) |
- return true; |
- |
- return false; |
-} |
- |
-int AccessibilityRenderObject::intValue() const |
-{ |
- if (!m_renderer || isPasswordField()) |
- return 0; |
- |
- if (isHeading()) |
- return headingLevel(m_renderer->element()); |
- |
- Node* node = m_renderer->element(); |
- if (!node || !isCheckboxOrRadio()) |
- return 0; |
- |
- // If this is an ARIA checkbox or radio, check the aria-checked attribute rather than node()->checked() |
- AccessibilityRole ariaRole = ariaRoleAttribute(); |
- if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) { |
- if (equalIgnoringCase(getAttribute(aria_checkedAttr).string(), "true")) |
- return true; |
- return false; |
- } |
- |
- return static_cast<HTMLInputElement*>(node)->checked(); |
-} |
- |
-float AccessibilityRenderObject::valueForRange() const |
-{ |
- if (!isProgressIndicator() && !isSlider()) |
- return 0.0f; |
- |
- return getAttribute(aria_valuenowAttr).toFloat(); |
-} |
- |
-float AccessibilityRenderObject::maxValueForRange() const |
-{ |
- if (!isProgressIndicator() && !isSlider()) |
- return 0.0f; |
- |
- return getAttribute(aria_valuemaxAttr).toFloat(); |
-} |
- |
-float AccessibilityRenderObject::minValueForRange() const |
-{ |
- if (!isProgressIndicator() && !isSlider()) |
- return 0.0f; |
- |
- return getAttribute(aria_valueminAttr).toFloat(); |
-} |
- |
-String AccessibilityRenderObject::stringValue() const |
-{ |
- if (!m_renderer || isPasswordField()) |
- return String(); |
- |
- if (m_renderer->isText()) |
- return textUnderElement(); |
- |
- if (m_renderer->isMenuList()) |
- return static_cast<RenderMenuList*>(m_renderer)->text(); |
- |
- if (m_renderer->isListMarker()) |
- return static_cast<RenderListMarker*>(m_renderer)->text(); |
- |
- if (isWebArea()) { |
- if (m_renderer->document()->frame()) |
- return String(); |
- |
- // FIXME: should use startOfDocument and endOfDocument (or rangeForDocument?) here |
- VisiblePosition startVisiblePosition = m_renderer->positionForCoordinates(0, 0); |
- VisiblePosition endVisiblePosition = m_renderer->positionForCoordinates(INT_MAX, INT_MAX); |
- if (startVisiblePosition.isNull() || endVisiblePosition.isNull()) |
- return String(); |
- |
- return plainText(makeRange(startVisiblePosition, endVisiblePosition).get()); |
- } |
- |
- if (isTextControl()) |
- return text(); |
- |
- if (isFileUploadButton()) { |
- RenderFileUploadControl* uploadControl = static_cast<RenderFileUploadControl*>(m_renderer); |
- return uploadControl->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; |
- // this would require subclassing or making accessibilityAttributeNames do something other than return a |
- // single static array. |
- return String(); |
-} |
- |
-// This function implements the ARIA accessible name as described by the Mozilla |
-// ARIA Implementer's Guide. |
-static String accessibleNameForNode(Node* node) |
-{ |
- if (node->isTextNode()) |
- return static_cast<Text*>(node)->data(); |
- |
- if (node->hasTagName(inputTag)) |
- return static_cast<HTMLInputElement*>(node)->value(); |
- |
- if (node->isHTMLElement()) { |
- const AtomicString& alt = static_cast<HTMLElement*>(node)->getAttribute(altAttr); |
- if (!alt.isEmpty()) |
- return alt; |
- } |
- |
- return String(); |
-} |
- |
-String AccessibilityRenderObject::ariaAccessiblityName(const String& s) const |
-{ |
- Document* document = m_renderer->document(); |
- if (!document) |
- return String(); |
- |
- String idList = s; |
- idList.replace('\n', ' '); |
- Vector<String> idVector; |
- idList.split(' ', idVector); |
- |
- Vector<UChar> ariaLabel; |
- unsigned size = idVector.size(); |
- for (unsigned i = 0; i < size; ++i) { |
- String idName = idVector[i]; |
- Element* idElement = document->getElementById(idName); |
- if (idElement) { |
- String nameFragment = accessibleNameForNode(idElement); |
- ariaLabel.append(nameFragment.characters(), nameFragment.length()); |
- for (Node* n = idElement->firstChild(); n; n = n->traverseNextNode(idElement->nextSibling())) { |
- nameFragment = accessibleNameForNode(n); |
- ariaLabel.append(nameFragment.characters(), nameFragment.length()); |
- } |
- ariaLabel.append(' '); |
- } |
- } |
- return String::adopt(ariaLabel); |
-} |
- |
-String AccessibilityRenderObject::ariaLabeledByAttribute() const |
-{ |
- Node* node = m_renderer->node(); |
- if (!node) |
- return String(); |
- |
- if (!node->isElementNode()) |
- return String(); |
- |
- // The ARIA spec uses the British spelling: "labelled." It seems prudent to support the American |
- // spelling ("labeled") as well. |
- String idList = getAttribute(aria_labeledbyAttr).string(); |
- if (idList.isEmpty()) { |
- idList = getAttribute(aria_labelledbyAttr).string(); |
- if (idList.isEmpty()) |
- return String(); |
- } |
- |
- return ariaAccessiblityName(idList); |
-} |
- |
-static HTMLLabelElement* labelForElement(Element* element) |
-{ |
- RefPtr<NodeList> list = element->document()->getElementsByTagName("label"); |
- unsigned len = list->length(); |
- for (unsigned i = 0; i < len; i++) { |
- if (list->item(i)->hasTagName(labelTag)) { |
- HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i)); |
- if (label->correspondingControl() == element) |
- return label; |
- } |
- } |
- |
- return 0; |
-} |
- |
-HTMLLabelElement* AccessibilityRenderObject::labelElementContainer() const |
-{ |
- if (!m_renderer) |
- return false; |
- |
- // the control element should not be considered part of the label |
- if (isControl()) |
- return false; |
- |
- // find if this has a parent that is a label |
- for (Node* parentNode = m_renderer->element(); parentNode; parentNode = parentNode->parentNode()) { |
- if (parentNode->hasTagName(labelTag)) |
- return static_cast<HTMLLabelElement*>(parentNode); |
- } |
- |
- return 0; |
-} |
- |
-String AccessibilityRenderObject::title() const |
-{ |
- AccessibilityRole ariaRole = ariaRoleAttribute(); |
- |
- if (!m_renderer) |
- return String(); |
- |
- Node* node = m_renderer->element(); |
- if (!node) |
- return String(); |
- |
- String ariaLabel = ariaLabeledByAttribute(); |
- if (!ariaLabel.isEmpty()) |
- return ariaLabel; |
- |
- const AtomicString& title = getAttribute(titleAttr); |
- if (!title.isEmpty()) |
- return title; |
- |
- bool isInputTag = node->hasTagName(inputTag); |
- if (isInputTag) { |
- HTMLInputElement* input = static_cast<HTMLInputElement*>(node); |
- if (input->isTextButton()) |
- return input->value(); |
- } |
- |
- if (isInputTag || AccessibilityObject::isARIAInput(ariaRole) || isControl()) { |
- HTMLLabelElement* label = labelForElement(static_cast<Element*>(node)); |
- if (label && !titleUIElement()) |
- return label->innerText(); |
- } |
- |
- if (roleValue() == ButtonRole |
- || ariaRole == ListBoxOptionRole |
- || ariaRole == MenuItemRole |
- || ariaRole == MenuButtonRole |
- || isHeading()) |
- return textUnderElement(); |
- |
- if (isLink()) |
- return textUnderElement(); |
- |
- return String(); |
-} |
- |
-String AccessibilityRenderObject::ariaDescribedByAttribute() const |
-{ |
- String idList = getAttribute(aria_describedbyAttr).string(); |
- if (idList.isEmpty()) |
- return String(); |
- |
- return ariaAccessiblityName(idList); |
-} |
- |
-String AccessibilityRenderObject::accessibilityDescription() const |
-{ |
- if (!m_renderer) |
- return String(); |
- |
- String ariaDescription = ariaDescribedByAttribute(); |
- if (!ariaDescription.isEmpty()) |
- return ariaDescription; |
- |
- if (isImage()) { |
- if (m_renderer->element() && m_renderer->element()->isHTMLElement()) { |
- const AtomicString& alt = static_cast<HTMLElement*>(m_renderer->element())->getAttribute(altAttr); |
- if (alt.isEmpty()) |
- return String(); |
- return alt; |
- } |
- } |
- |
- if (isWebArea()) { |
- Document *document = m_renderer->document(); |
- Node* owner = document->ownerElement(); |
- if (owner) { |
- if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) { |
- const AtomicString& title = static_cast<HTMLFrameElementBase*>(owner)->getAttribute(titleAttr); |
- if (!title.isEmpty()) |
- return title; |
- return static_cast<HTMLFrameElementBase*>(owner)->name(); |
- } |
- if (owner->isHTMLElement()) |
- return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr); |
- } |
- owner = document->body(); |
- if (owner && owner->isHTMLElement()) |
- return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr); |
- } |
- |
- if (roleValue() == DefinitionListTermRole) |
- return AXDefinitionListTermText(); |
- if (roleValue() == DefinitionListDefinitionRole) |
- return AXDefinitionListDefinitionText(); |
- |
- return String(); |
-} |
- |
-IntRect AccessibilityRenderObject::boundingBoxRect() const |
-{ |
- IntRect rect; |
- RenderObject* obj = m_renderer; |
- |
- if (!obj) |
- return IntRect(); |
- |
- if (obj->element()) // If we are a continuation, we want to make sure to use the primary renderer. |
- obj = obj->element()->renderer(); |
- |
- // FIXME: This doesn't work correctly with transforms. |
- Vector<IntRect> rects; |
- FloatPoint absPos = obj->localToAbsolute(); |
- obj->absoluteRects(rects, absPos.x(), absPos.y()); |
- const size_t n = rects.size(); |
- for (size_t i = 0; i < n; ++i) { |
- IntRect r = rects[i]; |
- if (!r.isEmpty()) { |
- if (obj->style()->hasAppearance()) |
- theme()->adjustRepaintRect(obj, r); |
- rect.unite(r); |
- } |
- } |
- return rect; |
-} |
- |
-IntRect AccessibilityRenderObject::checkboxOrRadioRect() const |
-{ |
- if (!m_renderer) |
- return IntRect(); |
- |
- HTMLLabelElement* label = labelForElement(static_cast<Element*>(m_renderer->element())); |
- if (!label || !label->renderer()) |
- return boundingBoxRect(); |
- |
- IntRect labelRect = axObjectCache()->get(label->renderer())->elementRect(); |
- labelRect.unite(boundingBoxRect()); |
- return labelRect; |
-} |
- |
-IntRect AccessibilityRenderObject::elementRect() const |
-{ |
- // a checkbox or radio button should encompass its label |
- if (isCheckboxOrRadio()) |
- return checkboxOrRadioRect(); |
- |
- return boundingBoxRect(); |
-} |
- |
-IntSize AccessibilityRenderObject::size() const |
-{ |
- IntRect rect = elementRect(); |
- return rect.size(); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const |
-{ |
- Element* element = anchorElement(); |
- if (!element) |
- return 0; |
- |
- // Right now, we do not support ARIA links as internal link elements |
- if (!element->hasTagName(aTag)) |
- return 0; |
- HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(element); |
- |
- KURL linkURL = anchor->href(); |
- String ref = linkURL.ref(); |
- if (ref.isEmpty()) |
- return 0; |
- |
- // check if URL is the same as current URL |
- linkURL.removeRef(); |
- if (m_renderer->document()->url() != linkURL) |
- return 0; |
- |
- Node* linkedNode = m_renderer->document()->findAnchor(ref); |
- if (!linkedNode) |
- return 0; |
- |
- // the element we find may not be accessible, keep searching until we find a good one |
- AccessibilityObject* linkedAXElement = m_renderer->document()->axObjectCache()->get(linkedNode->renderer()); |
- while (linkedAXElement && linkedAXElement->accessibilityIsIgnored()) { |
- linkedNode = linkedNode->traverseNextNode(); |
- |
- while (linkedNode && !linkedNode->renderer()) |
- linkedNode = linkedNode->traverseNextSibling(); |
- |
- if (!linkedNode) |
- return 0; |
- linkedAXElement = m_renderer->document()->axObjectCache()->get(linkedNode->renderer()); |
- } |
- |
- return linkedAXElement; |
-} |
- |
-void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const |
-{ |
- if (!m_renderer || roleValue() != RadioButtonRole) |
- return; |
- |
- Node* node = m_renderer->node(); |
- if (!node || !node->hasTagName(inputTag)) |
- return; |
- |
- HTMLInputElement* input = static_cast<HTMLInputElement*>(node); |
- // if there's a form, then this is easy |
- if (input->form()) { |
- Vector<RefPtr<Node> > formElements; |
- input->form()->getNamedElements(input->name(), formElements); |
- |
- unsigned len = formElements.size(); |
- for (unsigned i = 0; i < len; ++i) { |
- Node* associateElement = formElements[i].get(); |
- if (AccessibilityObject* object = m_renderer->document()->axObjectCache()->get(associateElement->renderer())) |
- linkedUIElements.append(object); |
- } |
- } else { |
- RefPtr<NodeList> list = node->document()->getElementsByTagName("input"); |
- unsigned len = list->length(); |
- for (unsigned i = 0; i < len; ++i) { |
- if (list->item(i)->hasTagName(inputTag)) { |
- HTMLInputElement* associateElement = static_cast<HTMLInputElement*>(list->item(i)); |
- if (associateElement->isRadioButton() && associateElement->name() == input->name()) { |
- if (AccessibilityObject* object = m_renderer->document()->axObjectCache()->get(associateElement->renderer())) |
- linkedUIElements.append(object); |
- } |
- } |
- } |
- } |
-} |
- |
-// linked ui elements could be all the related radio buttons in a group |
-// or an internal anchor connection |
-void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& linkedUIElements) const |
-{ |
- if (isAnchor()) { |
- AccessibilityObject* linkedAXElement = internalLinkElement(); |
- if (linkedAXElement) |
- linkedUIElements.append(linkedAXElement); |
- } |
- |
- if (roleValue() == RadioButtonRole) |
- addRadioButtonGroupMembers(linkedUIElements); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::titleUIElement() const |
-{ |
- if (!m_renderer) |
- return 0; |
- |
- // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset |
- if (isFieldset()) |
- return axObjectCache()->get(static_cast<RenderFieldset*>(m_renderer)->findLegend()); |
- |
- // checkbox and radio hide their labels. Only controls get titleUIElements for now |
- if (isCheckboxOrRadio() || !isControl()) |
- return 0; |
- |
- Node* element = m_renderer->element(); |
- HTMLLabelElement* label = labelForElement(static_cast<Element*>(element)); |
- if (label && label->renderer()) |
- return axObjectCache()->get(label->renderer()); |
- |
- return 0; |
-} |
- |
-bool AccessibilityRenderObject::accessibilityIsIgnored() const |
-{ |
- // ignore invisible element |
- if (!m_renderer || m_renderer->style()->visibility() != VISIBLE) |
- return true; |
- |
- if (isPresentationalChildOfAriaRole()) |
- return true; |
- |
- // ignore popup menu items because AppKit does |
- for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { |
- if (parent->isMenuList()) |
- return true; |
- } |
- |
- // find out if this element is inside of a label element. |
- // if so, it may be ignored because it's the label for a checkbox or radio button |
- HTMLLabelElement* labelElement = labelElementContainer(); |
- if (labelElement) { |
- HTMLElement* correspondingControl = labelElement->correspondingControl(); |
- if (correspondingControl && correspondingControl->renderer()) { |
- AccessibilityObject* controlObject = axObjectCache()->get(correspondingControl->renderer()); |
- if (controlObject->isCheckboxOrRadio()) |
- return true; |
- } |
- } |
- |
- AccessibilityRole ariaRole = ariaRoleAttribute(); |
- if (ariaRole == TextAreaRole || ariaRole == StaticTextRole) { |
- String ariaText = text(); |
- return ariaText.isNull() || ariaText.isEmpty(); |
- } |
- |
- // NOTE: BRs always have text boxes now, so the text box check here can be removed |
- if (m_renderer->isText()) { |
- // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level |
- if (parentObjectUnignored()->ariaRoleAttribute() == MenuItemRole || |
- parentObjectUnignored()->ariaRoleAttribute() == MenuButtonRole) |
- return true; |
- return m_renderer->isBR() || !toRenderText(m_renderer)->firstTextBox(); |
- } |
- |
- if (isHeading()) |
- return false; |
- |
- if (isLink()) |
- return false; |
- |
- // all controls are accessible |
- if (isControl()) |
- return false; |
- |
- // don't ignore labels, because they serve as TitleUIElements |
- Node* node = m_renderer->element(); |
- if (node && node->hasTagName(labelTag)) |
- return false; |
- |
- if (m_renderer->isBlockFlow() && m_renderer->childrenInline()) |
- return !toRenderBlock(m_renderer)->firstLineBox() && !mouseButtonListener(); |
- |
- // ignore images seemingly used as spacers |
- if (isImage()) { |
- if (node && node->isElementNode()) { |
- Element* elt = static_cast<Element*>(node); |
- const AtomicString& alt = elt->getAttribute(altAttr); |
- // don't ignore an image that has an alt tag |
- if (!alt.isEmpty()) |
- return false; |
- // informal standard is to ignore images with zero-length alt strings |
- if (!alt.isNull()) |
- return true; |
- } |
- |
- // check for one-dimensional image |
- RenderImage* image = toRenderImage(m_renderer); |
- if (image->height() <= 1 || image->width() <= 1) |
- return true; |
- |
- // check whether rendered image was stretched from one-dimensional file image |
- if (isNativeImage()) { |
- if (image->cachedImage()) { |
- IntSize imageSize = image->cachedImage()->imageSize(image->view()->zoomFactor()); |
- return imageSize.height() <= 1 || imageSize.width() <= 1; |
- } |
- } |
- return false; |
- } |
- |
- if (ariaRole != UnknownRole) |
- return false; |
- |
- // make a platform-specific decision |
- if (isAttachment()) |
- return accessibilityIgnoreAttachment(); |
- |
- return !m_renderer->isListMarker() && !isWebArea(); |
-} |
- |
-bool AccessibilityRenderObject::isLoaded() const |
-{ |
- return !m_renderer->document()->tokenizer(); |
-} |
- |
-int AccessibilityRenderObject::layoutCount() const |
-{ |
- if (!m_renderer->isRenderView()) |
- return 0; |
- return toRenderView(m_renderer)->frameView()->layoutCount(); |
-} |
- |
-String AccessibilityRenderObject::text() const |
-{ |
- if (!isTextControl() || isPasswordField()) |
- return String(); |
- |
- if (isNativeTextControl()) |
- return static_cast<RenderTextControl*>(m_renderer)->text(); |
- |
- Node* node = m_renderer->element(); |
- if (!node) |
- return String(); |
- if (!node->isElementNode()) |
- return String(); |
- |
- return static_cast<Element*>(node)->innerText(); |
-} |
- |
-int AccessibilityRenderObject::textLength() const |
-{ |
- ASSERT(isTextControl()); |
- |
- if (isPasswordField()) |
- return -1; // need to return something distinct from 0 |
- |
- return text().length(); |
-} |
- |
-PassRefPtr<Range> AccessibilityRenderObject::ariaSelectedTextDOMRange() const |
-{ |
- Node* node = m_renderer->element(); |
- if (!node) |
- return 0; |
- |
- RefPtr<Range> currentSelectionRange = selection().toRange(); |
- if (!currentSelectionRange) |
- return 0; |
- |
- ExceptionCode ec = 0; |
- if (!currentSelectionRange->intersectsNode(node, ec)) |
- return Range::create(currentSelectionRange->ownerDocument()); |
- |
- RefPtr<Range> ariaRange = rangeOfContents(node); |
- Position startPosition, endPosition; |
- |
- // Find intersection of currentSelectionRange and ariaRange |
- if (ariaRange->startOffset() > currentSelectionRange->startOffset()) |
- startPosition = ariaRange->startPosition(); |
- else |
- startPosition = currentSelectionRange->startPosition(); |
- |
- if (ariaRange->endOffset() < currentSelectionRange->endOffset()) |
- endPosition = ariaRange->endPosition(); |
- else |
- endPosition = currentSelectionRange->endPosition(); |
- |
- return Range::create(ariaRange->ownerDocument(), startPosition, endPosition); |
-} |
- |
-String AccessibilityRenderObject::selectedText() const |
-{ |
- ASSERT(isTextControl()); |
- |
- if (isPasswordField()) |
- return String(); // need to return something distinct from empty string |
- |
- if (isNativeTextControl()) { |
- RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); |
- return textControl->text().substring(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart()); |
- } |
- |
- if (ariaRoleAttribute() == UnknownRole) |
- return String(); |
- |
- RefPtr<Range> ariaRange = ariaSelectedTextDOMRange(); |
- if (!ariaRange) |
- return String(); |
- return ariaRange->text(); |
-} |
- |
-const AtomicString& AccessibilityRenderObject::accessKey() const |
-{ |
- Node* node = m_renderer->element(); |
- if (!node) |
- return nullAtom; |
- if (!node->isElementNode()) |
- return nullAtom; |
- return static_cast<Element*>(node)->getAttribute(accesskeyAttr); |
-} |
- |
-Selection AccessibilityRenderObject::selection() const |
-{ |
- return m_renderer->document()->frame()->selection()->selection(); |
-} |
- |
-PlainTextRange AccessibilityRenderObject::selectedTextRange() const |
-{ |
- ASSERT(isTextControl()); |
- |
- if (isPasswordField()) |
- return PlainTextRange(); |
- |
- AccessibilityRole ariaRole = ariaRoleAttribute(); |
- if (isNativeTextControl() && ariaRole == UnknownRole) { |
- RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); |
- return PlainTextRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart()); |
- } |
- |
- if (ariaRole == UnknownRole) |
- return PlainTextRange(); |
- |
- RefPtr<Range> ariaRange = ariaSelectedTextDOMRange(); |
- if (!ariaRange) |
- return PlainTextRange(); |
- return PlainTextRange(ariaRange->startOffset(), ariaRange->endOffset()); |
-} |
- |
-void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range) |
-{ |
- if (isNativeTextControl()) { |
- RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); |
- textControl->setSelectionRange(range.start, range.start + range.length); |
- return; |
- } |
- |
- Document* document = m_renderer->document(); |
- if (!document) |
- return; |
- Frame* frame = document->frame(); |
- if (!frame) |
- return; |
- Node* node = m_renderer->element(); |
- frame->selection()->setSelection(Selection(Position(node, range.start), |
- Position(node, range.start + range.length), DOWNSTREAM)); |
-} |
- |
-KURL AccessibilityRenderObject::url() const |
-{ |
- if (isAnchor() && m_renderer->element()->hasTagName(aTag)) { |
- if (HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(anchorElement())) |
- return anchor->href(); |
- } |
- |
- if (isWebArea()) |
- return m_renderer->document()->url(); |
- |
- if (isImage() && m_renderer->element() && m_renderer->element()->hasTagName(imgTag)) |
- return static_cast<HTMLImageElement*>(m_renderer->element())->src(); |
- |
- if (isInputImage()) |
- return static_cast<HTMLInputElement*>(m_renderer->element())->src(); |
- |
- return KURL(); |
-} |
- |
-bool AccessibilityRenderObject::isVisited() const |
-{ |
- return m_renderer->style()->pseudoState() == PseudoVisited; |
-} |
- |
-bool AccessibilityRenderObject::isSelected() const |
-{ |
- if (!m_renderer) |
- return false; |
- |
- Node* node = m_renderer->node(); |
- if (!node) |
- return false; |
- |
- return false; |
-} |
- |
-bool AccessibilityRenderObject::isFocused() const |
-{ |
- if (!m_renderer) |
- return false; |
- |
- Document* document = m_renderer->document(); |
- if (!document) |
- return false; |
- |
- Node* focusedNode = document->focusedNode(); |
- if (!focusedNode) |
- return false; |
- |
- // A web area is represented by the Document node in the DOM tree, which isn't focusable. |
- // Check instead if the frame's selection controller is focused |
- if (focusedNode == m_renderer->element() || |
- (roleValue() == WebAreaRole && document->frame()->selection()->isFocusedAndActive())) |
- return true; |
- |
- return false; |
-} |
- |
-void AccessibilityRenderObject::setFocused(bool on) |
-{ |
- if (!canSetFocusAttribute()) |
- return; |
- |
- if (!on) |
- m_renderer->document()->setFocusedNode(0); |
- else { |
- if (m_renderer->element()->isElementNode()) |
- static_cast<Element*>(m_renderer->element())->focus(); |
- else |
- m_renderer->document()->setFocusedNode(m_renderer->element()); |
- } |
-} |
- |
-void AccessibilityRenderObject::setValue(const String& string) |
-{ |
- // FIXME: Do we want to do anything here for ARIA textboxes? |
- if (m_renderer->isTextField()) { |
- HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); |
- input->setValue(string); |
- } else if (m_renderer->isTextArea()) { |
- HTMLTextAreaElement* textArea = static_cast<HTMLTextAreaElement*>(m_renderer->element()); |
- textArea->setValue(string); |
- } |
-} |
- |
-bool AccessibilityRenderObject::isEnabled() const |
-{ |
- ASSERT(m_renderer); |
- if (!m_renderer->element() || !m_renderer->element()->isElementNode()) |
- return true; |
- |
- FormControlElement* formControlElement = toFormControlElement(static_cast<Element*>(m_renderer->element())); |
- if (!formControlElement) |
- return true; |
- |
- return formControlElement->isEnabled(); |
-} |
- |
-RenderView* AccessibilityRenderObject::topRenderer() const |
-{ |
- return m_renderer->document()->topDocument()->renderView(); |
-} |
- |
-Document* AccessibilityRenderObject::document() const |
-{ |
- return m_renderer->document(); |
-} |
- |
-FrameView* AccessibilityRenderObject::topDocumentFrameView() const |
-{ |
- return topRenderer()->view()->frameView(); |
-} |
- |
-Widget* AccessibilityRenderObject::widget() const |
-{ |
- if (!m_renderer->isWidget()) |
- return 0; |
- |
- return static_cast<RenderWidget*>(m_renderer)->widget(); |
-} |
- |
-AXObjectCache* AccessibilityRenderObject::axObjectCache() const |
-{ |
- return m_renderer->document()->axObjectCache(); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const |
-{ |
- // find an image that is using this map |
- if (!m_renderer || !map) |
- return 0; |
- |
- RefPtr<HTMLCollection> coll = m_renderer->document()->images(); |
- for (Node* curr = coll->firstItem(); curr; curr = coll->nextItem()) { |
- RenderObject* obj = curr->renderer(); |
- if (!obj || !curr->hasTagName(imgTag)) |
- continue; |
- |
- // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning, |
- // which has to be stripped off |
- if (static_cast<HTMLImageElement*>(curr)->useMap().substring(1) == map->getName()) |
- return axObjectCache()->get(obj); |
- } |
- |
- return 0; |
-} |
- |
-void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result) |
-{ |
- Document* document = m_renderer->document(); |
- RefPtr<HTMLCollection> coll = document->links(); |
- Node* curr = coll->firstItem(); |
- while (curr) { |
- RenderObject* obj = curr->renderer(); |
- if (obj) { |
- RefPtr<AccessibilityObject> axobj = document->axObjectCache()->get(obj); |
- ASSERT(axobj); |
- ASSERT(axobj->roleValue() == WebCoreLinkRole); |
- if (!axobj->accessibilityIsIgnored()) |
- result.append(axobj); |
- } else { |
- Node* parent = curr->parent(); |
- if (parent && curr->hasTagName(areaTag) && parent->hasTagName(mapTag)) { |
- AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->get(ImageMapLinkRole)); |
- areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(curr)); |
- areaObject->setHTMLMapElement(static_cast<HTMLMapElement*>(parent)); |
- areaObject->setParent(accessibilityParentForImageMap(static_cast<HTMLMapElement*>(parent))); |
- |
- result.append(areaObject); |
- } |
- } |
- curr = coll->nextItem(); |
- } |
-} |
- |
-FrameView* AccessibilityRenderObject::documentFrameView() const |
-{ |
- if (!m_renderer || !m_renderer->document()) |
- return 0; |
- |
- // this is the RenderObject's Document's Frame's FrameView |
- return m_renderer->document()->view(); |
-} |
- |
-Widget* AccessibilityRenderObject::widgetForAttachmentView() const |
-{ |
- if (!isAttachment()) |
- return 0; |
- return static_cast<RenderWidget*>(m_renderer)->widget(); |
-} |
- |
-FrameView* AccessibilityRenderObject::frameViewIfRenderView() const |
-{ |
- if (!m_renderer->isRenderView()) |
- return 0; |
- // this is the RenderObject's Document's renderer's FrameView |
- return m_renderer->view()->frameView(); |
-} |
- |
-// This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns |
-// a Range that we can convert to a WebCoreTextMarkerRange in the Obj-C file |
-VisiblePositionRange AccessibilityRenderObject::visiblePositionRange() const |
-{ |
- if (!m_renderer) |
- return VisiblePositionRange(); |
- |
- // construct VisiblePositions for start and end |
- Node* node = m_renderer->element(); |
- if (!node) |
- return VisiblePositionRange(); |
- |
- VisiblePosition startPos = VisiblePosition(node, 0, VP_DEFAULT_AFFINITY); |
- VisiblePosition endPos = VisiblePosition(node, maxDeepOffset(node), VP_DEFAULT_AFFINITY); |
- |
- // the VisiblePositions are equal for nodes like buttons, so adjust for that |
- if (startPos == endPos) { |
- endPos = endPos.next(); |
- if (endPos.isNull()) |
- endPos = startPos; |
- } |
- |
- return VisiblePositionRange(startPos, endPos); |
-} |
- |
-VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsigned lineCount) const |
-{ |
- if (lineCount == 0 || !m_renderer) |
- return VisiblePositionRange(); |
- |
- // 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->document()->renderer()->positionForCoordinates(0, 0); |
- VisiblePosition savedVisiblePos; |
- while (--lineCount != 0) { |
- savedVisiblePos = visiblePos; |
- visiblePos = nextLinePosition(visiblePos, 0); |
- if (visiblePos.isNull() || visiblePos == savedVisiblePos) |
- return VisiblePositionRange(); |
- } |
- |
- // make a caret selection for the marker position, then extend it to the line |
- // NOTE: ignores results of sel.modify because it returns false when |
- // starting at an empty line. The resulting selection in that case |
- // will be a caret at visiblePos. |
- SelectionController selection; |
- selection.setSelection(Selection(visiblePos)); |
- selection.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary); |
- |
- return VisiblePositionRange(selection.selection().visibleStart(), selection.selection().visibleEnd()); |
-} |
- |
-VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) const |
-{ |
- if (!m_renderer) |
- return VisiblePosition(); |
- |
- if (isNativeTextControl()) |
- return static_cast<RenderTextControl*>(m_renderer)->visiblePositionForIndex(index); |
- |
- if (!isTextControl() && !m_renderer->isText()) |
- return VisiblePosition(); |
- |
- Node* node = m_renderer->node(); |
- if (!node) |
- return VisiblePosition(); |
- |
- if (index <= 0) |
- return VisiblePosition(node, 0, DOWNSTREAM); |
- |
- ExceptionCode ec = 0; |
- RefPtr<Range> range = Range::create(m_renderer->document()); |
- range->selectNodeContents(node, ec); |
- CharacterIterator it(range.get()); |
- it.advance(index - 1); |
- return VisiblePosition(it.range()->endContainer(ec), it.range()->endOffset(ec), UPSTREAM); |
-} |
- |
-int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const |
-{ |
- if (isNativeTextControl()) |
- return static_cast<RenderTextControl*>(m_renderer)->indexForVisiblePosition(pos); |
- |
- if (!isTextControl()) |
- return 0; |
- |
- Node* node = m_renderer->node(); |
- if (!node) |
- return 0; |
- |
- Position indexPosition = pos.deepEquivalent(); |
- if (!indexPosition.node() || indexPosition.node()->rootEditableElement() != node) |
- return 0; |
- |
- ExceptionCode ec = 0; |
- RefPtr<Range> range = Range::create(m_renderer->document()); |
- range->setStart(node, 0, ec); |
- range->setEnd(indexPosition.node(), indexPosition.offset(), ec); |
- return TextIterator::rangeLength(range.get()); |
-} |
- |
-IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const |
-{ |
- if (visiblePositionRange.isNull()) |
- return IntRect(); |
- |
- // Create a mutable VisiblePositionRange. |
- VisiblePositionRange range(visiblePositionRange); |
- IntRect rect1 = range.start.absoluteCaretBounds(); |
- IntRect rect2 = range.end.absoluteCaretBounds(); |
- |
- // 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()) { |
- VisiblePosition endOfFirstLine = endOfLine(range.start); |
- if (range.start == endOfFirstLine) { |
- range.start.setAffinity(DOWNSTREAM); |
- rect1 = range.start.absoluteCaretBounds(); |
- } |
- if (range.end == endOfFirstLine) { |
- range.end.setAffinity(UPSTREAM); |
- rect2 = range.end.absoluteCaretBounds(); |
- } |
- } |
- |
- IntRect ourrect = rect1; |
- ourrect.unite(rect2); |
- |
- // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead |
- if (rect1.bottom() != rect2.bottom()) { |
- RefPtr<Range> dataRange = makeRange(range.start, range.end); |
- IntRect boundingBox = dataRange->boundingBox(); |
- String rangeString = plainText(dataRange.get()); |
- if (rangeString.length() > 1 && !boundingBox.isEmpty()) |
- ourrect = boundingBox; |
- } |
- |
-#if PLATFORM(MAC) |
- return m_renderer->document()->view()->contentsToScreen(ourrect); |
-#else |
- return ourrect; |
-#endif |
-} |
- |
-void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const |
-{ |
- if (range.start.isNull() || range.end.isNull()) |
- 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) { |
- m_renderer->document()->frame()->selection()->moveTo(range.start, true); |
- } |
- else { |
- Selection newSelection = Selection(range.start, range.end); |
- m_renderer->document()->frame()->selection()->setSelection(newSelection); |
- } |
-} |
- |
-VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoint& point) const |
-{ |
- // convert absolute point to view coordinates |
- FrameView* frameView = m_renderer->document()->topDocument()->renderer()->view()->frameView(); |
- RenderView* renderView = topRenderer(); |
- Node* innerNode = 0; |
- |
- // locate the node containing the point |
- IntPoint pointResult; |
- while (1) { |
- IntPoint ourpoint; |
-#if PLATFORM(MAC) |
- ourpoint = frameView->screenToContents(point); |
-#else |
- ourpoint = point; |
-#endif |
- HitTestRequest request(HitTestRequest::ReadOnly | |
- HitTestRequest::Active); |
- HitTestResult result(ourpoint); |
- renderView->layer()->hitTest(request, result); |
- innerNode = result.innerNode(); |
- if (!innerNode || !innerNode->renderer()) |
- return VisiblePosition(); |
- |
- pointResult = result.localPoint(); |
- |
- // done if hit something other than a widget |
- RenderObject* renderer = innerNode->renderer(); |
- if (!renderer->isWidget()) |
- break; |
- |
- // descend into widget (FRAME, IFRAME, OBJECT...) |
- Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); |
- if (!widget || !widget->isFrameView()) |
- break; |
- Frame* frame = static_cast<FrameView*>(widget)->frame(); |
- if (!frame) |
- break; |
- Document* document = frame->document(); |
- if (!document) |
- break; |
- renderView = document->renderView(); |
- frameView = static_cast<FrameView*>(widget); |
- } |
- |
- return innerNode->renderer()->positionForPoint(pointResult); |
-} |
- |
-// NOTE: Consider providing this utility method as AX API |
-VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const |
-{ |
- if (!isTextControl()) |
- return VisiblePosition(); |
- |
- // lastIndexOK specifies whether the position after the last character is acceptable |
- if (indexValue >= text().length()) { |
- if (!lastIndexOK || indexValue > text().length()) |
- return VisiblePosition(); |
- } |
- VisiblePosition position = visiblePositionForIndex(indexValue); |
- position.setAffinity(DOWNSTREAM); |
- return position; |
-} |
- |
-// NOTE: Consider providing this utility method as AX API |
-int AccessibilityRenderObject::index(const VisiblePosition& position) const |
-{ |
- if (!isTextControl()) |
- return -1; |
- |
- Node* node = position.deepEquivalent().node(); |
- if (!node) |
- return -1; |
- |
- for (RenderObject* renderer = node->renderer(); renderer && renderer->element(); renderer = renderer->parent()) { |
- if (renderer == m_renderer) |
- return indexForVisiblePosition(position); |
- } |
- |
- return -1; |
-} |
- |
-// Given a line number, the range of characters of the text associated with this accessibility |
-// object that contains the line number. |
-PlainTextRange AccessibilityRenderObject::doAXRangeForLine(unsigned lineNumber) const |
-{ |
- if (!isTextControl()) |
- return PlainTextRange(); |
- |
- // iterate to the specified line |
- VisiblePosition visiblePos = visiblePositionForIndex(0); |
- VisiblePosition savedVisiblePos; |
- for (unsigned lineCount = lineNumber; lineCount != 0; lineCount -= 1) { |
- savedVisiblePos = visiblePos; |
- visiblePos = nextLinePosition(visiblePos, 0); |
- if (visiblePos.isNull() || visiblePos == savedVisiblePos) |
- return PlainTextRange(); |
- } |
- |
- // make a caret selection for the marker position, then extend it to the line |
- // NOTE: ignores results of selection.modify because it returns false when |
- // starting at an empty line. The resulting selection in that case |
- // will be a caret at visiblePos. |
- SelectionController selection; |
- selection.setSelection(Selection(visiblePos)); |
- selection.modify(SelectionController::EXTEND, SelectionController::LEFT, LineBoundary); |
- selection.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary); |
- |
- // calculate the indices for the selection start and end |
- VisiblePosition startPosition = selection.selection().visibleStart(); |
- VisiblePosition endPosition = selection.selection().visibleEnd(); |
- int index1 = indexForVisiblePosition(startPosition); |
- int index2 = indexForVisiblePosition(endPosition); |
- |
- // add one to the end index for a line break not caused by soft line wrap (to match AppKit) |
- if (endPosition.affinity() == DOWNSTREAM && endPosition.next().isNotNull()) |
- index2 += 1; |
- |
- // return nil rather than an zero-length range (to match AppKit) |
- if (index1 == index2) |
- return PlainTextRange(); |
- |
- return PlainTextRange(index1, index2 - index1); |
-} |
- |
-// The composed character range in the text associated with this accessibility object that |
-// is specified by the given index value. This parameterized attribute returns the complete |
-// range of characters (including surrogate pairs of multi-byte glyphs) at the given index. |
-PlainTextRange AccessibilityRenderObject::doAXRangeForIndex(unsigned index) const |
-{ |
- if (!isTextControl()) |
- return PlainTextRange(); |
- |
- String elementText = text(); |
- if (!elementText.length() || index > elementText.length() - 1) |
- return PlainTextRange(); |
- |
- return PlainTextRange(index, 1); |
-} |
- |
-// A substring of the text associated with this accessibility object that is |
-// specified by the given character range. |
-String AccessibilityRenderObject::doAXStringForRange(const PlainTextRange& range) const |
-{ |
- if (isPasswordField()) |
- return String(); |
- |
- if (range.length == 0) |
- return ""; |
- |
- if (!isTextControl()) |
- return String(); |
- |
- String elementText = text(); |
- if (range.start + range.length > elementText.length()) |
- return String(); |
- |
- return elementText.substring(range.start, range.length); |
-} |
- |
-// The bounding rectangle of the text associated with this accessibility object that is |
-// specified by the given range. This is the bounding rectangle a sighted user would see |
-// on the display screen, in pixels. |
-IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& range) const |
-{ |
- if (isTextControl()) |
- return boundsForVisiblePositionRange(visiblePositionRangeForRange(range)); |
- return IntRect(); |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::doAccessibilityHitTest(const IntPoint& point) const |
-{ |
- if (!m_renderer || !m_renderer->hasLayer()) |
- return 0; |
- |
- RenderLayer* layer = toRenderBox(m_renderer)->layer(); |
- |
- HitTestRequest request(HitTestRequest::ReadOnly | |
- HitTestRequest::Active); |
- HitTestResult hitTestResult = HitTestResult(point); |
- layer->hitTest(request, hitTestResult); |
- if (!hitTestResult.innerNode()) |
- return 0; |
- Node* node = hitTestResult.innerNode()->shadowAncestorNode(); |
- RenderObject* obj = node->renderer(); |
- if (!obj) |
- return 0; |
- |
- AccessibilityObject *result = obj->document()->axObjectCache()->get(obj); |
- |
- if (obj->isListBox()) |
- return static_cast<AccessibilityListBox*>(result)->doAccessibilityHitTest(point); |
- |
- if (result->accessibilityIsIgnored()) |
- result = result->parentObjectUnignored(); |
- |
- return result; |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::focusedUIElement() const |
-{ |
- // get the focused node in the page |
- Page* page = m_renderer->document()->page(); |
- if (!page) |
- return 0; |
- |
- Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document(); |
- Node* focusedNode = focusedDocument->focusedNode(); |
- if (!focusedNode) |
- focusedNode = focusedDocument; |
- |
- RenderObject* focusedNodeRenderer = focusedNode->renderer(); |
- if (!focusedNodeRenderer) |
- return 0; |
- |
- AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->get(focusedNodeRenderer); |
- |
- if (obj->shouldFocusActiveDescendant()) { |
- if (AccessibilityObject* descendant = obj->activeDescendant()) |
- obj = descendant; |
- } |
- |
- // the HTML element, for example, is focusable but has an AX object that is ignored |
- if (obj->accessibilityIsIgnored()) |
- obj = obj->parentObjectUnignored(); |
- |
- return obj; |
-} |
- |
-bool AccessibilityRenderObject::shouldFocusActiveDescendant() const |
-{ |
- switch (ariaRoleAttribute()) { |
- case GroupRole: |
- case ComboBoxRole: |
- case ListBoxRole: |
- case MenuRole: |
- case MenuBarRole: |
- case RadioGroupRole: |
- case RowRole: |
- case PopUpButtonRole: |
- case ProgressIndicatorRole: |
- case ToolbarRole: |
- case OutlineRole: |
- /* FIXME: replace these with actual roles when they are added to AccessibilityRole |
- composite |
- alert |
- alertdialog |
- grid |
- status |
- timer |
- tree |
- */ |
- return true; |
- default: |
- return false; |
- } |
-} |
- |
-AccessibilityObject* AccessibilityRenderObject::activeDescendant() const |
-{ |
- if (renderer()->element() && !renderer()->element()->isElementNode()) |
- return 0; |
- Element* element = static_cast<Element*>(renderer()->element()); |
- |
- String activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr).string(); |
- if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) |
- return 0; |
- |
- Element* target = renderer()->document()->getElementById(activeDescendantAttrStr); |
- if (!target) |
- return 0; |
- |
- AccessibilityObject* obj = renderer()->document()->axObjectCache()->get(target->renderer()); |
- if (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; |
-} |
- |
- |
-void AccessibilityRenderObject::handleActiveDescendantChanged() |
-{ |
- Element* element = static_cast<Element*>(renderer()->element()); |
- if (!element) |
- return; |
- Document* doc = renderer()->document(); |
- if (!doc->frame()->selection()->isFocusedAndActive() || doc->focusedNode() != element) |
- return; |
- AccessibilityRenderObject* activedescendant = static_cast<AccessibilityRenderObject*>(activeDescendant()); |
- |
- if (activedescendant && shouldFocusActiveDescendant()) |
- doc->axObjectCache()->postNotificationToElement(activedescendant->renderer(), "AXFocusedUIElementChanged"); |
-} |
- |
- |
-AccessibilityObject* AccessibilityRenderObject::observableObject() const |
-{ |
- for (RenderObject* renderer = m_renderer; renderer && renderer->element(); renderer = renderer->parent()) { |
- if (renderer->isTextField() || renderer->isTextArea()) |
- return renderer->document()->axObjectCache()->get(renderer); |
- } |
- |
- return 0; |
-} |
- |
-typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; |
- |
-static const ARIARoleMap& createARIARoleMap() |
-{ |
- struct RoleEntry { |
- String ariaRole; |
- AccessibilityRole webcoreRole; |
- }; |
- |
- const RoleEntry roles[] = { |
- { "button", ButtonRole }, |
- { "checkbox", CheckBoxRole }, |
- { "group", GroupRole }, |
- { "heading", HeadingRole }, |
- { "img", ImageRole }, |
- { "link", WebCoreLinkRole }, |
- { "listbox", ListBoxRole }, |
- // "option" isn't here because it may map to different roles depending on the parent element's role |
- { "menu", MenuRole }, |
- { "menubar", GroupRole }, |
- // "menuitem" isn't here because it may map to different roles depending on the parent element's role |
- { "menuitemcheckbox", MenuItemRole }, |
- { "menuitemradio", MenuItemRole }, |
- { "progressbar", ProgressIndicatorRole }, |
- { "radio", RadioButtonRole }, |
- { "range", SliderRole }, |
- { "slider", SliderRole }, |
- { "spinbutton", ProgressIndicatorRole }, |
- { "textbox", TextAreaRole } |
- }; |
- ARIARoleMap& roleMap = *new ARIARoleMap; |
- |
- const unsigned numRoles = sizeof(roles) / sizeof(roles[0]); |
- for (unsigned i = 0; i < numRoles; ++i) |
- roleMap.set(roles[i].ariaRole, roles[i].webcoreRole); |
- return roleMap; |
-} |
- |
-static AccessibilityRole ariaRoleToWebCoreRole(String value) |
-{ |
- ASSERT(!value.isEmpty() && !value.isNull()); |
- static const ARIARoleMap& roleMap = createARIARoleMap(); |
- return roleMap.get(value); |
-} |
- |
-AccessibilityRole AccessibilityRenderObject::determineAriaRoleAttribute() const |
-{ |
- String ariaRole = getAttribute(roleAttr).string(); |
- if (ariaRole.isNull() || ariaRole.isEmpty()) |
- return UnknownRole; |
- |
- AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole); |
- if (role) |
- return role; |
- // selects and listboxes both have options as child roles, but they map to different roles within WebCore |
- if (equalIgnoringCase(ariaRole,"option")) { |
- if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole) |
- return MenuItemRole; |
- if (parentObjectUnignored()->ariaRoleAttribute() == ListBoxRole) |
- return ListBoxOptionRole; |
- } |
- // an aria "menuitem" may map to MenuButton or MenuItem depending on its parent |
- if (equalIgnoringCase(ariaRole,"menuitem")) { |
- if (parentObjectUnignored()->ariaRoleAttribute() == GroupRole) |
- return MenuButtonRole; |
- if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole) |
- return MenuItemRole; |
- } |
- |
- return UnknownRole; |
-} |
- |
-void AccessibilityRenderObject::setAriaRole() |
-{ |
- m_ariaRole = determineAriaRoleAttribute(); |
-} |
- |
-AccessibilityRole AccessibilityRenderObject::ariaRoleAttribute() const |
-{ |
- return m_ariaRole; |
-} |
- |
-AccessibilityRole AccessibilityRenderObject::roleValue() const |
-{ |
- if (!m_renderer) |
- return UnknownRole; |
- |
- Node* node = m_renderer->element(); |
- AccessibilityRole ariaRole = ariaRoleAttribute(); |
- if (ariaRole != UnknownRole) |
- return ariaRole; |
- |
- if (node && node->isLink()) { |
- if (m_renderer->isImage()) |
- return ImageMapRole; |
- return WebCoreLinkRole; |
- } |
- if (m_renderer->isListMarker()) |
- return ListMarkerRole; |
- if (node && node->hasTagName(buttonTag)) |
- return ButtonRole; |
- if (m_renderer->isText()) |
- return StaticTextRole; |
- if (m_renderer->isImage()) { |
- if (node && node->hasTagName(inputTag)) |
- return ButtonRole; |
- return ImageRole; |
- } |
- if (m_renderer->isRenderView()) |
- return WebAreaRole; |
- |
- if (m_renderer->isTextField()) |
- return TextFieldRole; |
- |
- if (m_renderer->isTextArea()) |
- return TextAreaRole; |
- |
- if (node && node->hasTagName(inputTag)) { |
- HTMLInputElement* input = static_cast<HTMLInputElement*>(node); |
- if (input->inputType() == HTMLInputElement::CHECKBOX) |
- return CheckBoxRole; |
- if (input->inputType() == HTMLInputElement::RADIO) |
- return RadioButtonRole; |
- if (input->isTextButton()) |
- return ButtonRole; |
- } |
- |
- if (node && node->hasTagName(buttonTag)) |
- return ButtonRole; |
- |
- if (isFileUploadButton()) |
- return ButtonRole; |
- |
- if (m_renderer->isMenuList()) |
- return PopUpButtonRole; |
- |
- if (headingLevel(m_renderer->element()) != 0) |
- return HeadingRole; |
- |
- if (node && node->hasTagName(ddTag)) |
- return DefinitionListDefinitionRole; |
- |
- if (node && node->hasTagName(dtTag)) |
- return DefinitionListTermRole; |
- |
- if (m_renderer->isBlockFlow() || (node && node->hasTagName(labelTag))) |
- return GroupRole; |
- |
- return UnknownRole; |
-} |
- |
-bool AccessibilityRenderObject::isPresentationalChildOfAriaRole() const |
-{ |
- // Walk the parent chain looking for a parent that has presentational children |
- AccessibilityObject* parent; |
- for (parent = parentObject(); parent && !parent->ariaRoleHasPresentationalChildren(); parent = parent->parentObject()) |
- ; |
- return parent; |
-} |
- |
-bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const |
-{ |
- switch (m_ariaRole) { |
- case ButtonRole: |
- case SliderRole: |
- case ImageRole: |
- case ProgressIndicatorRole: |
- //case SeparatorRole: |
- return true; |
- default: |
- return false; |
- } |
-} |
- |
-bool AccessibilityRenderObject::canSetFocusAttribute() const |
-{ |
- ASSERT(m_renderer); |
- |
- // NOTE: It would be more accurate to ask the document whether setFocusedNode() would |
- // do anything. For example, it setFocusedNode() will do nothing if the current focused |
- // node will not relinquish the focus. |
- if (!m_renderer->element() || !m_renderer->element()->isElementNode()) |
- return false; |
- |
- FormControlElement* formControlElement = toFormControlElement(static_cast<Element*>(m_renderer->element())); |
- if (formControlElement && !formControlElement->isEnabled()) |
- return false; |
- |
- switch (roleValue()) { |
- case WebCoreLinkRole: |
- case ImageMapLinkRole: |
- case TextFieldRole: |
- case TextAreaRole: |
- case ButtonRole: |
- case PopUpButtonRole: |
- case CheckBoxRole: |
- case RadioButtonRole: |
- return true; |
- default: |
- return false; |
- } |
-} |
- |
-bool AccessibilityRenderObject::canSetValueAttribute() const |
-{ |
- if (isWebArea()) |
- return !isReadOnly(); |
- |
- return isTextControl() || isProgressIndicator() || isSlider(); |
-} |
- |
-bool AccessibilityRenderObject::canSetTextRangeAttributes() const |
-{ |
- return isTextControl(); |
-} |
- |
-void AccessibilityRenderObject::childrenChanged() |
-{ |
- // this method is meant as a quick way of marking dirty |
- // a portion of the accessibility tree |
- |
- markChildrenDirty(); |
- |
- // this object may not be accessible (and thus may not appear |
- // in the hierarchy), which means we need to go up the parent |
- // chain and mark the parent's dirty. Ideally, we would want |
- // to only access the next object that is not ignored, but |
- // asking an element if it's ignored can lead to an examination of the |
- // render tree which is dangerous. |
- for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { |
- if (parent->isAccessibilityRenderObject()) |
- static_cast<AccessibilityRenderObject *>(parent)->markChildrenDirty(); |
- } |
-} |
- |
-bool AccessibilityRenderObject::canHaveChildren() const |
-{ |
- if (!m_renderer) |
- return false; |
- |
- // Elements that should not have children |
- switch (roleValue()) { |
- case ImageRole: |
- case ButtonRole: |
- case PopUpButtonRole: |
- case CheckBoxRole: |
- case RadioButtonRole: |
- return false; |
- default: |
- return true; |
- } |
-} |
- |
-const AccessibilityObject::AccessibilityChildrenVector& AccessibilityRenderObject::children() |
-{ |
- if (m_childrenDirty) { |
- clearChildren(); |
- m_childrenDirty = false; |
- } |
- |
- if (!m_haveChildren) |
- addChildren(); |
- return m_children; |
-} |
- |
-void AccessibilityRenderObject::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); |
- |
- // nothing to add if there is no RenderObject |
- if (!m_renderer) |
- return; |
- |
- m_haveChildren = true; |
- |
- if (!canHaveChildren()) |
- return; |
- |
- // add all unignored acc children |
- for (RefPtr<AccessibilityObject> obj = firstChild(); obj; obj = obj->nextSibling()) { |
- if (obj->accessibilityIsIgnored()) { |
- if (!obj->hasChildren()) |
- obj->addChildren(); |
- AccessibilityChildrenVector children = obj->children(); |
- unsigned length = children.size(); |
- for (unsigned i = 0; i < length; ++i) |
- m_children.append(children[i]); |
- } else |
- m_children.append(obj); |
- } |
- |
- // for a RenderImage, add the <area> elements as individual accessibility objects |
- if (m_renderer->isRenderImage()) { |
- HTMLMapElement* map = toRenderImage(m_renderer)->imageMap(); |
- if (map) { |
- for (Node* current = map->firstChild(); current; current = current->traverseNextNode(map)) { |
- |
- // add an <area> element for this child if it has a link |
- if (current->isLink()) { |
- AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(m_renderer->document()->axObjectCache()->get(ImageMapLinkRole)); |
- areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(current)); |
- areaObject->setHTMLMapElement(map); |
- areaObject->setParent(this); |
- |
- m_children.append(areaObject); |
- } |
- } |
- } |
- } |
-} |
- |
-void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& result) |
-{ |
- AccessibilityObject* child = firstChild(); |
- bool isMultiselectable = false; |
- |
- Element* element = static_cast<Element*>(renderer()->element()); |
- if (!element || !element->isElementNode()) // do this check to ensure safety of static_cast above |
- return; |
- |
- String multiselectablePropertyStr = element->getAttribute("aria-multiselectable").string(); |
- isMultiselectable = equalIgnoringCase(multiselectablePropertyStr, "true"); |
- |
- while (child) { |
- // every child should have aria-role option, and if so, check for selected attribute/state |
- AccessibilityRole ariaRole = child->ariaRoleAttribute(); |
- RenderObject* childRenderer = 0; |
- if (child->isAccessibilityRenderObject()) |
- childRenderer = static_cast<AccessibilityRenderObject*>(child)->renderer(); |
- if (childRenderer && ariaRole == ListBoxOptionRole) { |
- Element* childElement = static_cast<Element*>(childRenderer->element()); |
- if (childElement && childElement->isElementNode()) { // do this check to ensure safety of static_cast above |
- String selectedAttrString = childElement->getAttribute("aria-selected").string(); |
- if (equalIgnoringCase(selectedAttrString, "true")) { |
- result.append(child); |
- if (isMultiselectable) |
- return; |
- } |
- } |
- } |
- child = child->nextSibling(); |
- } |
-} |
- |
-void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& result) |
-{ |
- ASSERT(result.isEmpty()); |
- |
- // only listboxes should be asked for their selected children. |
- if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes |
- ASSERT_NOT_REACHED(); |
- return; |
- } |
- return ariaListboxSelectedChildren(result); |
-} |
- |
-void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result) |
-{ |
- if (!hasChildren()) |
- addChildren(); |
- |
- unsigned length = m_children.size(); |
- for (unsigned i = 0; i < length; i++) { |
- if (!m_children[i]->isOffScreen()) |
- result.append(m_children[i]); |
- } |
-} |
- |
-void AccessibilityRenderObject::visibleChildren(AccessibilityChildrenVector& result) |
-{ |
- ASSERT(result.isEmpty()); |
- |
- // only listboxes are asked for their visible children. |
- if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes |
- ASSERT_NOT_REACHED(); |
- return; |
- } |
- return ariaListboxVisibleChildren(result); |
-} |
- |
-void AccessibilityRenderObject::removeAXObjectID() |
-{ |
- if (!m_id) |
- return; |
-#if PLATFORM(MAC) |
- m_renderer->document()->axObjectCache()->removeAXID(this); |
-#endif |
-} |
- |
-const String& AccessibilityRenderObject::actionVerb() const |
-{ |
- // 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())); |
- DEFINE_STATIC_LOCAL(const String, noAction, ()); |
- |
- switch (roleValue()) { |
- case ButtonRole: |
- return buttonAction; |
- case TextFieldRole: |
- case TextAreaRole: |
- return textFieldAction; |
- case RadioButtonRole: |
- return radioButtonAction; |
- case CheckBoxRole: |
- return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; |
- case LinkRole: |
- case WebCoreLinkRole: |
- return linkAction; |
- default: |
- return noAction; |
- } |
-} |
- |
-void AccessibilityRenderObject::updateBackingStore() |
-{ |
- if (!m_renderer) |
- return; |
- m_renderer->view()->layoutIfNeeded(); |
-} |
- |
-} // namespace WebCore |
+/* |
+* Copyright (C) 2008 Apple 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 "AccessibilityRenderObject.h" |
+ |
+#include "AXObjectCache.h" |
+#include "AccessibilityListBox.h" |
+#include "AccessibilityImageMapLink.h" |
+#include "CharacterNames.h" |
+#include "EventNames.h" |
+#include "FloatRect.h" |
+#include "FocusController.h" |
+#include "Frame.h" |
+#include "FrameLoader.h" |
+#include "HTMLAreaElement.h" |
+#include "HTMLFrameElementBase.h" |
+#include "HTMLImageElement.h" |
+#include "HTMLInputElement.h" |
+#include "HTMLLabelElement.h" |
+#include "HTMLMapElement.h" |
+#include "HTMLOptGroupElement.h" |
+#include "HTMLOptionElement.h" |
+#include "HTMLOptionsCollection.h" |
+#include "HTMLSelectElement.h" |
+#include "HTMLTextAreaElement.h" |
+#include "HitTestRequest.h" |
+#include "HitTestResult.h" |
+#include "LocalizedStrings.h" |
+#include "NodeList.h" |
+#include "NotImplemented.h" |
+#include "Page.h" |
+#include "RenderFieldset.h" |
+#include "RenderFileUploadControl.h" |
+#include "RenderImage.h" |
+#include "RenderInline.h" |
+#include "RenderListBox.h" |
+#include "RenderListMarker.h" |
+#include "RenderMenuList.h" |
+#include "RenderText.h" |
+#include "RenderTextControl.h" |
+#include "RenderTheme.h" |
+#include "RenderView.h" |
+#include "RenderWidget.h" |
+#include "SelectionController.h" |
+#include "Text.h" |
+#include "TextIterator.h" |
+#include "htmlediting.h" |
+#include "visible_units.h" |
+#include <wtf/StdLibExtras.h> |
+ |
+using namespace std; |
+ |
+namespace WebCore { |
+ |
+using namespace HTMLNames; |
+ |
+AccessibilityRenderObject::AccessibilityRenderObject(RenderObject* renderer) |
+ : m_renderer(renderer) |
+ , m_ariaRole(UnknownRole) |
+{ |
+ setAriaRole(); |
+#ifndef NDEBUG |
+ m_renderer->setHasAXObject(true); |
+#endif |
+} |
+ |
+AccessibilityRenderObject::~AccessibilityRenderObject() |
+{ |
+ ASSERT(isDetached()); |
+} |
+ |
+PassRefPtr<AccessibilityRenderObject> AccessibilityRenderObject::create(RenderObject* renderer) |
+{ |
+ return adoptRef(new AccessibilityRenderObject(renderer)); |
+} |
+ |
+void AccessibilityRenderObject::detach() |
+{ |
+ clearChildren(); |
+ AccessibilityObject::detach(); |
+ |
+#ifndef NDEBUG |
+ if (m_renderer) |
+ m_renderer->setHasAXObject(false); |
+#endif |
+ m_renderer = 0; |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::firstChild() const |
+{ |
+ if (!m_renderer) |
+ return 0; |
+ |
+ RenderObject* firstChild = m_renderer->firstChild(); |
+ if (!firstChild) |
+ return 0; |
+ |
+ return m_renderer->document()->axObjectCache()->get(firstChild); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::lastChild() const |
+{ |
+ if (!m_renderer) |
+ return 0; |
+ |
+ RenderObject* lastChild = m_renderer->lastChild(); |
+ if (!lastChild) |
+ return 0; |
+ |
+ return m_renderer->document()->axObjectCache()->get(lastChild); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::previousSibling() const |
+{ |
+ if (!m_renderer) |
+ return 0; |
+ |
+ RenderObject* previousSibling = m_renderer->previousSibling(); |
+ if (!previousSibling) |
+ return 0; |
+ |
+ return m_renderer->document()->axObjectCache()->get(previousSibling); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::nextSibling() const |
+{ |
+ if (!m_renderer) |
+ return 0; |
+ |
+ RenderObject* nextSibling = m_renderer->nextSibling(); |
+ if (!nextSibling) |
+ return 0; |
+ |
+ return m_renderer->document()->axObjectCache()->get(nextSibling); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::parentObject() const |
+{ |
+ if (!m_renderer) |
+ return 0; |
+ |
+ RenderObject *parent = m_renderer->parent(); |
+ if (!parent) |
+ return 0; |
+ |
+ if (ariaRoleAttribute() == MenuBarRole) |
+ return m_renderer->document()->axObjectCache()->get(parent); |
+ |
+ // menuButton and its corresponding menu are DOM siblings, but Accessibility needs them to be parent/child |
+ if (ariaRoleAttribute() == MenuRole) { |
+ AccessibilityObject* parent = menuButtonForMenu(); |
+ if (parent) |
+ return parent; |
+ } |
+ |
+ return m_renderer->document()->axObjectCache()->get(parent); |
+} |
+ |
+bool AccessibilityRenderObject::isWebArea() const |
+{ |
+ return roleValue() == WebAreaRole; |
+} |
+ |
+bool AccessibilityRenderObject::isImageButton() const |
+{ |
+ return isNativeImage() && roleValue() == ButtonRole; |
+} |
+ |
+bool AccessibilityRenderObject::isAnchor() const |
+{ |
+ return !isNativeImage() && isLink(); |
+} |
+ |
+bool AccessibilityRenderObject::isNativeTextControl() const |
+{ |
+ return m_renderer->isTextField() || m_renderer->isTextArea(); |
+} |
+ |
+bool AccessibilityRenderObject::isTextControl() const |
+{ |
+ AccessibilityRole role = roleValue(); |
+ return role == TextAreaRole || role == TextFieldRole; |
+} |
+ |
+bool AccessibilityRenderObject::isNativeImage() const |
+{ |
+ return m_renderer->isImage(); |
+} |
+ |
+bool AccessibilityRenderObject::isImage() const |
+{ |
+ return roleValue() == ImageRole; |
+} |
+ |
+bool AccessibilityRenderObject::isAttachment() const |
+{ |
+ // Widgets are the replaced elements that we represent to AX as attachments |
+ bool isWidget = m_renderer && m_renderer->isWidget(); |
+ ASSERT(!isWidget || (m_renderer->isReplaced() && !isImage())); |
+ return isWidget && ariaRoleAttribute() == UnknownRole; |
+} |
+ |
+bool AccessibilityRenderObject::isPasswordField() const |
+{ |
+ ASSERT(m_renderer); |
+ if (!m_renderer->element() || !m_renderer->element()->isHTMLElement()) |
+ return false; |
+ if (ariaRoleAttribute() != UnknownRole) |
+ return false; |
+ |
+ InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->element())); |
+ if (!inputElement) |
+ return false; |
+ |
+ return inputElement->isPasswordField(); |
+} |
+ |
+bool AccessibilityRenderObject::isCheckboxOrRadio() const |
+{ |
+ AccessibilityRole role = roleValue(); |
+ return role == RadioButtonRole || role == CheckBoxRole; |
+} |
+ |
+bool AccessibilityRenderObject::isFileUploadButton() const |
+{ |
+ if (m_renderer && m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) { |
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); |
+ return input->inputType() == HTMLInputElement::FILE; |
+ } |
+ |
+ return false; |
+} |
+ |
+bool AccessibilityRenderObject::isInputImage() const |
+{ |
+ if (m_renderer && m_renderer->element() && m_renderer->element()->hasTagName(inputTag)) { |
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); |
+ return input->inputType() == HTMLInputElement::IMAGE; |
+ } |
+ |
+ return false; |
+} |
+ |
+bool AccessibilityRenderObject::isProgressIndicator() const |
+{ |
+ return roleValue() == ProgressIndicatorRole; |
+} |
+ |
+bool AccessibilityRenderObject::isSlider() const |
+{ |
+ return roleValue() == SliderRole; |
+} |
+ |
+bool AccessibilityRenderObject::isMenuRelated() const |
+{ |
+ AccessibilityRole role = roleValue(); |
+ return role == MenuRole || |
+ role == MenuBarRole || |
+ role == MenuButtonRole || |
+ role == MenuItemRole; |
+} |
+ |
+bool AccessibilityRenderObject::isMenu() const |
+{ |
+ return roleValue() == MenuRole; |
+} |
+ |
+bool AccessibilityRenderObject::isMenuBar() const |
+{ |
+ return roleValue() == MenuBarRole; |
+} |
+ |
+bool AccessibilityRenderObject::isMenuButton() const |
+{ |
+ return roleValue() == MenuButtonRole; |
+} |
+ |
+bool AccessibilityRenderObject::isMenuItem() const |
+{ |
+ return roleValue() == MenuItemRole; |
+} |
+ |
+bool AccessibilityRenderObject::isPressed() const |
+{ |
+ ASSERT(m_renderer); |
+ if (roleValue() != ButtonRole) |
+ return false; |
+ |
+ Node* node = m_renderer->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).string(), "true")) |
+ return true; |
+ return false; |
+ } |
+ |
+ return node->active(); |
+} |
+ |
+bool AccessibilityRenderObject::isIndeterminate() const |
+{ |
+ ASSERT(m_renderer); |
+ if (!m_renderer->node() || !m_renderer->node()->isElementNode()) |
+ return false; |
+ |
+ InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node())); |
+ if (!inputElement) |
+ return false; |
+ |
+ return inputElement->isIndeterminate(); |
+} |
+ |
+bool AccessibilityRenderObject::isChecked() const |
+{ |
+ ASSERT(m_renderer); |
+ if (!m_renderer->node() || !m_renderer->node()->isElementNode()) |
+ return false; |
+ |
+ InputElement* inputElement = toInputElement(static_cast<Element*>(m_renderer->node())); |
+ if (!inputElement) |
+ return false; |
+ |
+ return inputElement->isChecked(); |
+} |
+ |
+bool AccessibilityRenderObject::isHovered() const |
+{ |
+ ASSERT(m_renderer); |
+ return m_renderer->node() && m_renderer->node()->hovered(); |
+} |
+ |
+bool AccessibilityRenderObject::isMultiSelect() const |
+{ |
+ ASSERT(m_renderer); |
+ if (!m_renderer->isListBox()) |
+ return false; |
+ return m_renderer->element() && static_cast<HTMLSelectElement*>(m_renderer->element())->multiple(); |
+} |
+ |
+bool AccessibilityRenderObject::isReadOnly() const |
+{ |
+ ASSERT(m_renderer); |
+ |
+ if (isWebArea()) { |
+ Document* document = m_renderer->document(); |
+ if (!document) |
+ return true; |
+ |
+ HTMLElement* body = document->body(); |
+ if (body && body->isContentEditable()) |
+ return false; |
+ |
+ Frame* frame = document->frame(); |
+ if (!frame) |
+ return true; |
+ |
+ return !frame->isContentEditable(); |
+ } |
+ |
+ return !m_renderer->node() || !m_renderer->node()->isContentEditable(); |
+} |
+ |
+bool AccessibilityRenderObject::isOffScreen() const |
+{ |
+ ASSERT(m_renderer); |
+ IntRect contentRect = m_renderer->absoluteClippedOverflowRect(); |
+ FrameView* view = m_renderer->document()->frame()->view(); |
+ FloatRect viewRect = view->visibleContentRect(); |
+ viewRect.intersect(contentRect); |
+ return viewRect.isEmpty(); |
+} |
+ |
+int AccessibilityRenderObject::headingLevel(Node* node) |
+{ |
+ // headings can be in block flow and non-block flow |
+ if (!node) |
+ return 0; |
+ |
+ if (RenderObject* renderer = node->renderer()) { |
+ AccessibilityObject* axObjectForNode = node->document()->axObjectCache()->get(renderer); |
+ if (axObjectForNode->ariaRoleAttribute() == HeadingRole) { |
+ if (!node->isElementNode()) |
+ return 0; |
+ Element* element = static_cast<Element*>(node); |
+ return element->getAttribute(aria_levelAttr).toInt(); |
+ } |
+ } |
+ |
+ |
+ if (node->hasTagName(h1Tag)) |
+ return 1; |
+ |
+ if (node->hasTagName(h2Tag)) |
+ return 2; |
+ |
+ if (node->hasTagName(h3Tag)) |
+ return 3; |
+ |
+ if (node->hasTagName(h4Tag)) |
+ return 4; |
+ |
+ if (node->hasTagName(h5Tag)) |
+ return 5; |
+ |
+ if (node->hasTagName(h6Tag)) |
+ return 6; |
+ |
+ return 0; |
+} |
+ |
+bool AccessibilityRenderObject::isHeading() const |
+{ |
+ return roleValue() == HeadingRole; |
+} |
+ |
+bool AccessibilityRenderObject::isLink() const |
+{ |
+ return roleValue() == WebCoreLinkRole; |
+} |
+ |
+bool AccessibilityRenderObject::isControl() const |
+{ |
+ if (!m_renderer) |
+ return false; |
+ |
+ Node* node = m_renderer->element(); |
+ return node && ((node->isElementNode() && static_cast<Element*>(node)->isFormControlElement()) |
+ || AccessibilityObject::isARIAControl(ariaRoleAttribute())); |
+} |
+ |
+bool AccessibilityRenderObject::isFieldset() const |
+{ |
+ if (!m_renderer) |
+ return false; |
+ |
+ return m_renderer->isFieldset(); |
+} |
+ |
+bool AccessibilityRenderObject::isGroup() const |
+{ |
+ return roleValue() == GroupRole; |
+} |
+ |
+const AtomicString& AccessibilityRenderObject::getAttribute(const QualifiedName& attribute) const |
+{ |
+ Node* node = m_renderer->element(); |
+ if (!node) |
+ return nullAtom; |
+ |
+ if (!node->isElementNode()) |
+ return nullAtom; |
+ |
+ Element* element = static_cast<Element*>(node); |
+ return element->getAttribute(attribute); |
+} |
+ |
+Element* AccessibilityRenderObject::anchorElement() const |
+{ |
+ if (!m_renderer) |
+ return 0; |
+ |
+ AXObjectCache* cache = axObjectCache(); |
+ RenderObject* currRenderer; |
+ |
+ // Search up the render tree for a RenderObject with a DOM node. Defer to an earlier continuation, though. |
+ for (currRenderer = m_renderer; currRenderer && !currRenderer->element(); currRenderer = currRenderer->parent()) { |
+ if (currRenderer->isRenderBlock()) { |
+ RenderInline* continuation = toRenderBlock(currRenderer)->inlineContinuation(); |
+ if (continuation) |
+ return cache->get(continuation)->anchorElement(); |
+ } |
+ } |
+ |
+ // bail if none found |
+ if (!currRenderer) |
+ return 0; |
+ |
+ // 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 (node->hasTagName(aTag) || (node->renderer() && cache->get(node->renderer())->isAnchor())) |
+ return static_cast<Element*>(node); |
+ } |
+ |
+ return 0; |
+} |
+ |
+Element* AccessibilityRenderObject::actionElement() const |
+{ |
+ if (!m_renderer) |
+ return 0; |
+ |
+ Node* node = m_renderer->element(); |
+ if (node) { |
+ if (node->hasTagName(inputTag)) { |
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node); |
+ if (!input->disabled() && (isCheckboxOrRadio() || input->isTextButton())) |
+ return input; |
+ } else if (node->hasTagName(buttonTag)) |
+ return static_cast<Element*>(node); |
+ } |
+ |
+ if (isFileUploadButton()) |
+ return static_cast<Element*>(m_renderer->element()); |
+ |
+ if (AccessibilityObject::isARIAInput(ariaRoleAttribute())) |
+ return static_cast<Element*>(m_renderer->element()); |
+ |
+ if (isImageButton()) |
+ return static_cast<Element*>(m_renderer->element()); |
+ |
+ if (m_renderer->isMenuList()) |
+ return static_cast<RenderMenuList*>(m_renderer)->selectElement(); |
+ |
+ Element* elt = anchorElement(); |
+ if (!elt) |
+ elt = mouseButtonListener(); |
+ return elt; |
+} |
+ |
+Element* AccessibilityRenderObject::mouseButtonListener() const |
+{ |
+ Node* node = m_renderer->element(); |
+ if (!node) |
+ return 0; |
+ if (!node->isEventTargetNode()) |
+ return 0; |
+ |
+ // FIXME: Do the continuation search like anchorElement does |
+ for (EventTargetNode* elt = static_cast<EventTargetNode*>(node); elt; elt = static_cast<EventTargetNode*>(elt->parentNode())) { |
+ if (elt->inlineEventListenerForType(eventNames().clickEvent) || elt->inlineEventListenerForType(eventNames().mousedownEvent) || elt->inlineEventListenerForType(eventNames().mouseupEvent)) |
+ return static_cast<Element*>(elt); |
+ } |
+ |
+ return 0; |
+} |
+ |
+static Element* siblingWithAriaRole(String role, Node* node) |
+{ |
+ Node* sibling = node->parent()->firstChild(); |
+ while (sibling) { |
+ if (sibling->isElementNode()) { |
+ String siblingAriaRole = static_cast<Element*>(sibling)->getAttribute(roleAttr).string(); |
+ if (equalIgnoringCase(siblingAriaRole, role)) |
+ return static_cast<Element*>(sibling); |
+ } |
+ sibling = sibling->nextSibling(); |
+ } |
+ |
+ return 0; |
+} |
+ |
+Element* AccessibilityRenderObject::menuElementForMenuButton() const |
+{ |
+ if (ariaRoleAttribute() != MenuButtonRole) |
+ return 0; |
+ |
+ return siblingWithAriaRole("menu", renderer()->node()); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::menuForMenuButton() const |
+{ |
+ Element* menu = menuElementForMenuButton(); |
+ if (menu && menu->renderer()) |
+ return m_renderer->document()->axObjectCache()->get(menu->renderer()); |
+ return 0; |
+} |
+ |
+Element* AccessibilityRenderObject::menuItemElementForMenu() const |
+{ |
+ if (ariaRoleAttribute() != MenuRole) |
+ return 0; |
+ |
+ return siblingWithAriaRole("menuitem", renderer()->node()); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::menuButtonForMenu() const |
+{ |
+ Element* menuItem = menuItemElementForMenu(); |
+ |
+ if (menuItem && menuItem->renderer()) { |
+ // ARIA just has generic menu items. AppKit needs to know if this is a top level items like MenuBarButton or MenuBarItem |
+ AccessibilityObject* menuItemAX = m_renderer->document()->axObjectCache()->get(menuItem->renderer()); |
+ if (menuItemAX->isMenuButton()) |
+ return menuItemAX; |
+ } |
+ return 0; |
+} |
+ |
+String AccessibilityRenderObject::helpText() const |
+{ |
+ if (!m_renderer) |
+ return String(); |
+ |
+ for (RenderObject* curr = m_renderer; curr; curr = curr->parent()) { |
+ if (curr->element() && curr->element()->isHTMLElement()) { |
+ const AtomicString& summary = static_cast<Element*>(curr->element())->getAttribute(summaryAttr); |
+ if (!summary.isEmpty()) |
+ return summary; |
+ const AtomicString& title = static_cast<Element*>(curr->element())->getAttribute(titleAttr); |
+ if (!title.isEmpty()) |
+ return title; |
+ } |
+ } |
+ |
+ return String(); |
+} |
+ |
+String AccessibilityRenderObject::textUnderElement() const |
+{ |
+ if (!m_renderer) |
+ return String(); |
+ |
+ if (isFileUploadButton()) { |
+ RenderFileUploadControl* uploadControl = static_cast<RenderFileUploadControl*>(m_renderer); |
+ return uploadControl->buttonValue(); |
+ } |
+ |
+ Node* node = m_renderer->element(); |
+ if (node) { |
+ if (Frame* frame = node->document()->frame()) { |
+ // catch stale WebCoreAXObject (see <rdar://problem/3960196>) |
+ if (frame->document() != node->document()) |
+ return String(); |
+ return plainText(rangeOfContents(node).get()); |
+ } |
+ } |
+ |
+ // return the null string for anonymous text because it is non-trivial to get |
+ // the actual text and, so far, that is not needed |
+ return String(); |
+} |
+ |
+bool AccessibilityRenderObject::hasIntValue() const |
+{ |
+ if (isHeading()) |
+ return true; |
+ |
+ if (m_renderer->element() && isCheckboxOrRadio()) |
+ return true; |
+ |
+ return false; |
+} |
+ |
+int AccessibilityRenderObject::intValue() const |
+{ |
+ if (!m_renderer || isPasswordField()) |
+ return 0; |
+ |
+ if (isHeading()) |
+ return headingLevel(m_renderer->element()); |
+ |
+ Node* node = m_renderer->element(); |
+ if (!node || !isCheckboxOrRadio()) |
+ return 0; |
+ |
+ // If this is an ARIA checkbox or radio, check the aria-checked attribute rather than node()->checked() |
+ AccessibilityRole ariaRole = ariaRoleAttribute(); |
+ if (ariaRole == RadioButtonRole || ariaRole == CheckBoxRole) { |
+ if (equalIgnoringCase(getAttribute(aria_checkedAttr).string(), "true")) |
+ return true; |
+ return false; |
+ } |
+ |
+ return static_cast<HTMLInputElement*>(node)->checked(); |
+} |
+ |
+float AccessibilityRenderObject::valueForRange() const |
+{ |
+ if (!isProgressIndicator() && !isSlider()) |
+ return 0.0f; |
+ |
+ return getAttribute(aria_valuenowAttr).toFloat(); |
+} |
+ |
+float AccessibilityRenderObject::maxValueForRange() const |
+{ |
+ if (!isProgressIndicator() && !isSlider()) |
+ return 0.0f; |
+ |
+ return getAttribute(aria_valuemaxAttr).toFloat(); |
+} |
+ |
+float AccessibilityRenderObject::minValueForRange() const |
+{ |
+ if (!isProgressIndicator() && !isSlider()) |
+ return 0.0f; |
+ |
+ return getAttribute(aria_valueminAttr).toFloat(); |
+} |
+ |
+String AccessibilityRenderObject::stringValue() const |
+{ |
+ if (!m_renderer || isPasswordField()) |
+ return String(); |
+ |
+ if (m_renderer->isText()) |
+ return textUnderElement(); |
+ |
+ if (m_renderer->isMenuList()) |
+ return static_cast<RenderMenuList*>(m_renderer)->text(); |
+ |
+ if (m_renderer->isListMarker()) |
+ return static_cast<RenderListMarker*>(m_renderer)->text(); |
+ |
+ if (isWebArea()) { |
+ if (m_renderer->document()->frame()) |
+ return String(); |
+ |
+ // FIXME: should use startOfDocument and endOfDocument (or rangeForDocument?) here |
+ VisiblePosition startVisiblePosition = m_renderer->positionForCoordinates(0, 0); |
+ VisiblePosition endVisiblePosition = m_renderer->positionForCoordinates(INT_MAX, INT_MAX); |
+ if (startVisiblePosition.isNull() || endVisiblePosition.isNull()) |
+ return String(); |
+ |
+ return plainText(makeRange(startVisiblePosition, endVisiblePosition).get()); |
+ } |
+ |
+ if (isTextControl()) |
+ return text(); |
+ |
+ if (isFileUploadButton()) { |
+ RenderFileUploadControl* uploadControl = static_cast<RenderFileUploadControl*>(m_renderer); |
+ return uploadControl->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; |
+ // this would require subclassing or making accessibilityAttributeNames do something other than return a |
+ // single static array. |
+ return String(); |
+} |
+ |
+// This function implements the ARIA accessible name as described by the Mozilla |
+// ARIA Implementer's Guide. |
+static String accessibleNameForNode(Node* node) |
+{ |
+ if (node->isTextNode()) |
+ return static_cast<Text*>(node)->data(); |
+ |
+ if (node->hasTagName(inputTag)) |
+ return static_cast<HTMLInputElement*>(node)->value(); |
+ |
+ if (node->isHTMLElement()) { |
+ const AtomicString& alt = static_cast<HTMLElement*>(node)->getAttribute(altAttr); |
+ if (!alt.isEmpty()) |
+ return alt; |
+ } |
+ |
+ return String(); |
+} |
+ |
+String AccessibilityRenderObject::ariaAccessiblityName(const String& s) const |
+{ |
+ Document* document = m_renderer->document(); |
+ if (!document) |
+ return String(); |
+ |
+ String idList = s; |
+ idList.replace('\n', ' '); |
+ Vector<String> idVector; |
+ idList.split(' ', idVector); |
+ |
+ Vector<UChar> ariaLabel; |
+ unsigned size = idVector.size(); |
+ for (unsigned i = 0; i < size; ++i) { |
+ String idName = idVector[i]; |
+ Element* idElement = document->getElementById(idName); |
+ if (idElement) { |
+ String nameFragment = accessibleNameForNode(idElement); |
+ ariaLabel.append(nameFragment.characters(), nameFragment.length()); |
+ for (Node* n = idElement->firstChild(); n; n = n->traverseNextNode(idElement->nextSibling())) { |
+ nameFragment = accessibleNameForNode(n); |
+ ariaLabel.append(nameFragment.characters(), nameFragment.length()); |
+ } |
+ ariaLabel.append(' '); |
+ } |
+ } |
+ return String::adopt(ariaLabel); |
+} |
+ |
+String AccessibilityRenderObject::ariaLabeledByAttribute() const |
+{ |
+ Node* node = m_renderer->node(); |
+ if (!node) |
+ return String(); |
+ |
+ if (!node->isElementNode()) |
+ return String(); |
+ |
+ // The ARIA spec uses the British spelling: "labelled." It seems prudent to support the American |
+ // spelling ("labeled") as well. |
+ String idList = getAttribute(aria_labeledbyAttr).string(); |
+ if (idList.isEmpty()) { |
+ idList = getAttribute(aria_labelledbyAttr).string(); |
+ if (idList.isEmpty()) |
+ return String(); |
+ } |
+ |
+ return ariaAccessiblityName(idList); |
+} |
+ |
+static HTMLLabelElement* labelForElement(Element* element) |
+{ |
+ RefPtr<NodeList> list = element->document()->getElementsByTagName("label"); |
+ unsigned len = list->length(); |
+ for (unsigned i = 0; i < len; i++) { |
+ if (list->item(i)->hasTagName(labelTag)) { |
+ HTMLLabelElement* label = static_cast<HTMLLabelElement*>(list->item(i)); |
+ if (label->correspondingControl() == element) |
+ return label; |
+ } |
+ } |
+ |
+ return 0; |
+} |
+ |
+HTMLLabelElement* AccessibilityRenderObject::labelElementContainer() const |
+{ |
+ if (!m_renderer) |
+ return false; |
+ |
+ // the control element should not be considered part of the label |
+ if (isControl()) |
+ return false; |
+ |
+ // find if this has a parent that is a label |
+ for (Node* parentNode = m_renderer->element(); parentNode; parentNode = parentNode->parentNode()) { |
+ if (parentNode->hasTagName(labelTag)) |
+ return static_cast<HTMLLabelElement*>(parentNode); |
+ } |
+ |
+ return 0; |
+} |
+ |
+String AccessibilityRenderObject::title() const |
+{ |
+ AccessibilityRole ariaRole = ariaRoleAttribute(); |
+ |
+ if (!m_renderer) |
+ return String(); |
+ |
+ Node* node = m_renderer->element(); |
+ if (!node) |
+ return String(); |
+ |
+ String ariaLabel = ariaLabeledByAttribute(); |
+ if (!ariaLabel.isEmpty()) |
+ return ariaLabel; |
+ |
+ const AtomicString& title = getAttribute(titleAttr); |
+ if (!title.isEmpty()) |
+ return title; |
+ |
+ bool isInputTag = node->hasTagName(inputTag); |
+ if (isInputTag) { |
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node); |
+ if (input->isTextButton()) |
+ return input->value(); |
+ } |
+ |
+ if (isInputTag || AccessibilityObject::isARIAInput(ariaRole) || isControl()) { |
+ HTMLLabelElement* label = labelForElement(static_cast<Element*>(node)); |
+ if (label && !titleUIElement()) |
+ return label->innerText(); |
+ } |
+ |
+ if (roleValue() == ButtonRole |
+ || ariaRole == ListBoxOptionRole |
+ || ariaRole == MenuItemRole |
+ || ariaRole == MenuButtonRole |
+ || isHeading()) |
+ return textUnderElement(); |
+ |
+ if (isLink()) |
+ return textUnderElement(); |
+ |
+ return String(); |
+} |
+ |
+String AccessibilityRenderObject::ariaDescribedByAttribute() const |
+{ |
+ String idList = getAttribute(aria_describedbyAttr).string(); |
+ if (idList.isEmpty()) |
+ return String(); |
+ |
+ return ariaAccessiblityName(idList); |
+} |
+ |
+String AccessibilityRenderObject::accessibilityDescription() const |
+{ |
+ if (!m_renderer) |
+ return String(); |
+ |
+ String ariaDescription = ariaDescribedByAttribute(); |
+ if (!ariaDescription.isEmpty()) |
+ return ariaDescription; |
+ |
+ if (isImage()) { |
+ if (m_renderer->element() && m_renderer->element()->isHTMLElement()) { |
+ const AtomicString& alt = static_cast<HTMLElement*>(m_renderer->element())->getAttribute(altAttr); |
+ if (alt.isEmpty()) |
+ return String(); |
+ return alt; |
+ } |
+ } |
+ |
+ if (isWebArea()) { |
+ Document *document = m_renderer->document(); |
+ Node* owner = document->ownerElement(); |
+ if (owner) { |
+ if (owner->hasTagName(frameTag) || owner->hasTagName(iframeTag)) { |
+ const AtomicString& title = static_cast<HTMLFrameElementBase*>(owner)->getAttribute(titleAttr); |
+ if (!title.isEmpty()) |
+ return title; |
+ return static_cast<HTMLFrameElementBase*>(owner)->name(); |
+ } |
+ if (owner->isHTMLElement()) |
+ return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr); |
+ } |
+ owner = document->body(); |
+ if (owner && owner->isHTMLElement()) |
+ return static_cast<HTMLElement*>(owner)->getAttribute(nameAttr); |
+ } |
+ |
+ if (roleValue() == DefinitionListTermRole) |
+ return AXDefinitionListTermText(); |
+ if (roleValue() == DefinitionListDefinitionRole) |
+ return AXDefinitionListDefinitionText(); |
+ |
+ return String(); |
+} |
+ |
+IntRect AccessibilityRenderObject::boundingBoxRect() const |
+{ |
+ IntRect rect; |
+ RenderObject* obj = m_renderer; |
+ |
+ if (!obj) |
+ return IntRect(); |
+ |
+ if (obj->element()) // If we are a continuation, we want to make sure to use the primary renderer. |
+ obj = obj->element()->renderer(); |
+ |
+ // FIXME: This doesn't work correctly with transforms. |
+ Vector<IntRect> rects; |
+ FloatPoint absPos = obj->localToAbsolute(); |
+ obj->absoluteRects(rects, absPos.x(), absPos.y()); |
+ const size_t n = rects.size(); |
+ for (size_t i = 0; i < n; ++i) { |
+ IntRect r = rects[i]; |
+ if (!r.isEmpty()) { |
+ if (obj->style()->hasAppearance()) |
+ theme()->adjustRepaintRect(obj, r); |
+ rect.unite(r); |
+ } |
+ } |
+ return rect; |
+} |
+ |
+IntRect AccessibilityRenderObject::checkboxOrRadioRect() const |
+{ |
+ if (!m_renderer) |
+ return IntRect(); |
+ |
+ HTMLLabelElement* label = labelForElement(static_cast<Element*>(m_renderer->element())); |
+ if (!label || !label->renderer()) |
+ return boundingBoxRect(); |
+ |
+ IntRect labelRect = axObjectCache()->get(label->renderer())->elementRect(); |
+ labelRect.unite(boundingBoxRect()); |
+ return labelRect; |
+} |
+ |
+IntRect AccessibilityRenderObject::elementRect() const |
+{ |
+ // a checkbox or radio button should encompass its label |
+ if (isCheckboxOrRadio()) |
+ return checkboxOrRadioRect(); |
+ |
+ return boundingBoxRect(); |
+} |
+ |
+IntSize AccessibilityRenderObject::size() const |
+{ |
+ IntRect rect = elementRect(); |
+ return rect.size(); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::internalLinkElement() const |
+{ |
+ Element* element = anchorElement(); |
+ if (!element) |
+ return 0; |
+ |
+ // Right now, we do not support ARIA links as internal link elements |
+ if (!element->hasTagName(aTag)) |
+ return 0; |
+ HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(element); |
+ |
+ KURL linkURL = anchor->href(); |
+ String ref = linkURL.ref(); |
+ if (ref.isEmpty()) |
+ return 0; |
+ |
+ // check if URL is the same as current URL |
+ linkURL.removeRef(); |
+ if (m_renderer->document()->url() != linkURL) |
+ return 0; |
+ |
+ Node* linkedNode = m_renderer->document()->findAnchor(ref); |
+ if (!linkedNode) |
+ return 0; |
+ |
+ // the element we find may not be accessible, keep searching until we find a good one |
+ AccessibilityObject* linkedAXElement = m_renderer->document()->axObjectCache()->get(linkedNode->renderer()); |
+ while (linkedAXElement && linkedAXElement->accessibilityIsIgnored()) { |
+ linkedNode = linkedNode->traverseNextNode(); |
+ |
+ while (linkedNode && !linkedNode->renderer()) |
+ linkedNode = linkedNode->traverseNextSibling(); |
+ |
+ if (!linkedNode) |
+ return 0; |
+ linkedAXElement = m_renderer->document()->axObjectCache()->get(linkedNode->renderer()); |
+ } |
+ |
+ return linkedAXElement; |
+} |
+ |
+void AccessibilityRenderObject::addRadioButtonGroupMembers(AccessibilityChildrenVector& linkedUIElements) const |
+{ |
+ if (!m_renderer || roleValue() != RadioButtonRole) |
+ return; |
+ |
+ Node* node = m_renderer->node(); |
+ if (!node || !node->hasTagName(inputTag)) |
+ return; |
+ |
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node); |
+ // if there's a form, then this is easy |
+ if (input->form()) { |
+ Vector<RefPtr<Node> > formElements; |
+ input->form()->getNamedElements(input->name(), formElements); |
+ |
+ unsigned len = formElements.size(); |
+ for (unsigned i = 0; i < len; ++i) { |
+ Node* associateElement = formElements[i].get(); |
+ if (AccessibilityObject* object = m_renderer->document()->axObjectCache()->get(associateElement->renderer())) |
+ linkedUIElements.append(object); |
+ } |
+ } else { |
+ RefPtr<NodeList> list = node->document()->getElementsByTagName("input"); |
+ unsigned len = list->length(); |
+ for (unsigned i = 0; i < len; ++i) { |
+ if (list->item(i)->hasTagName(inputTag)) { |
+ HTMLInputElement* associateElement = static_cast<HTMLInputElement*>(list->item(i)); |
+ if (associateElement->isRadioButton() && associateElement->name() == input->name()) { |
+ if (AccessibilityObject* object = m_renderer->document()->axObjectCache()->get(associateElement->renderer())) |
+ linkedUIElements.append(object); |
+ } |
+ } |
+ } |
+ } |
+} |
+ |
+// linked ui elements could be all the related radio buttons in a group |
+// or an internal anchor connection |
+void AccessibilityRenderObject::linkedUIElements(AccessibilityChildrenVector& linkedUIElements) const |
+{ |
+ if (isAnchor()) { |
+ AccessibilityObject* linkedAXElement = internalLinkElement(); |
+ if (linkedAXElement) |
+ linkedUIElements.append(linkedAXElement); |
+ } |
+ |
+ if (roleValue() == RadioButtonRole) |
+ addRadioButtonGroupMembers(linkedUIElements); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::titleUIElement() const |
+{ |
+ if (!m_renderer) |
+ return 0; |
+ |
+ // if isFieldset is true, the renderer is guaranteed to be a RenderFieldset |
+ if (isFieldset()) |
+ return axObjectCache()->get(static_cast<RenderFieldset*>(m_renderer)->findLegend()); |
+ |
+ // checkbox and radio hide their labels. Only controls get titleUIElements for now |
+ if (isCheckboxOrRadio() || !isControl()) |
+ return 0; |
+ |
+ Node* element = m_renderer->element(); |
+ HTMLLabelElement* label = labelForElement(static_cast<Element*>(element)); |
+ if (label && label->renderer()) |
+ return axObjectCache()->get(label->renderer()); |
+ |
+ return 0; |
+} |
+ |
+bool AccessibilityRenderObject::accessibilityIsIgnored() const |
+{ |
+ // ignore invisible element |
+ if (!m_renderer || m_renderer->style()->visibility() != VISIBLE) |
+ return true; |
+ |
+ if (isPresentationalChildOfAriaRole()) |
+ return true; |
+ |
+ // ignore popup menu items because AppKit does |
+ for (RenderObject* parent = m_renderer->parent(); parent; parent = parent->parent()) { |
+ if (parent->isMenuList()) |
+ return true; |
+ } |
+ |
+ // find out if this element is inside of a label element. |
+ // if so, it may be ignored because it's the label for a checkbox or radio button |
+ HTMLLabelElement* labelElement = labelElementContainer(); |
+ if (labelElement) { |
+ HTMLElement* correspondingControl = labelElement->correspondingControl(); |
+ if (correspondingControl && correspondingControl->renderer()) { |
+ AccessibilityObject* controlObject = axObjectCache()->get(correspondingControl->renderer()); |
+ if (controlObject->isCheckboxOrRadio()) |
+ return true; |
+ } |
+ } |
+ |
+ AccessibilityRole ariaRole = ariaRoleAttribute(); |
+ if (ariaRole == TextAreaRole || ariaRole == StaticTextRole) { |
+ String ariaText = text(); |
+ return ariaText.isNull() || ariaText.isEmpty(); |
+ } |
+ |
+ // NOTE: BRs always have text boxes now, so the text box check here can be removed |
+ if (m_renderer->isText()) { |
+ // static text beneath MenuItems and MenuButtons are just reported along with the menu item, so it's ignored on an individual level |
+ if (parentObjectUnignored()->ariaRoleAttribute() == MenuItemRole || |
+ parentObjectUnignored()->ariaRoleAttribute() == MenuButtonRole) |
+ return true; |
+ return m_renderer->isBR() || !toRenderText(m_renderer)->firstTextBox(); |
+ } |
+ |
+ if (isHeading()) |
+ return false; |
+ |
+ if (isLink()) |
+ return false; |
+ |
+ // all controls are accessible |
+ if (isControl()) |
+ return false; |
+ |
+ // don't ignore labels, because they serve as TitleUIElements |
+ Node* node = m_renderer->element(); |
+ if (node && node->hasTagName(labelTag)) |
+ return false; |
+ |
+ if (m_renderer->isBlockFlow() && m_renderer->childrenInline()) |
+ return !toRenderBlock(m_renderer)->firstLineBox() && !mouseButtonListener(); |
+ |
+ // ignore images seemingly used as spacers |
+ if (isImage()) { |
+ if (node && node->isElementNode()) { |
+ Element* elt = static_cast<Element*>(node); |
+ const AtomicString& alt = elt->getAttribute(altAttr); |
+ // don't ignore an image that has an alt tag |
+ if (!alt.isEmpty()) |
+ return false; |
+ // informal standard is to ignore images with zero-length alt strings |
+ if (!alt.isNull()) |
+ return true; |
+ } |
+ |
+ // check for one-dimensional image |
+ RenderImage* image = toRenderImage(m_renderer); |
+ if (image->height() <= 1 || image->width() <= 1) |
+ return true; |
+ |
+ // check whether rendered image was stretched from one-dimensional file image |
+ if (isNativeImage()) { |
+ if (image->cachedImage()) { |
+ IntSize imageSize = image->cachedImage()->imageSize(image->view()->zoomFactor()); |
+ return imageSize.height() <= 1 || imageSize.width() <= 1; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ if (ariaRole != UnknownRole) |
+ return false; |
+ |
+ // make a platform-specific decision |
+ if (isAttachment()) |
+ return accessibilityIgnoreAttachment(); |
+ |
+ return !m_renderer->isListMarker() && !isWebArea(); |
+} |
+ |
+bool AccessibilityRenderObject::isLoaded() const |
+{ |
+ return !m_renderer->document()->tokenizer(); |
+} |
+ |
+int AccessibilityRenderObject::layoutCount() const |
+{ |
+ if (!m_renderer->isRenderView()) |
+ return 0; |
+ return toRenderView(m_renderer)->frameView()->layoutCount(); |
+} |
+ |
+String AccessibilityRenderObject::text() const |
+{ |
+ if (!isTextControl() || isPasswordField()) |
+ return String(); |
+ |
+ if (isNativeTextControl()) |
+ return static_cast<RenderTextControl*>(m_renderer)->text(); |
+ |
+ Node* node = m_renderer->element(); |
+ if (!node) |
+ return String(); |
+ if (!node->isElementNode()) |
+ return String(); |
+ |
+ return static_cast<Element*>(node)->innerText(); |
+} |
+ |
+int AccessibilityRenderObject::textLength() const |
+{ |
+ ASSERT(isTextControl()); |
+ |
+ if (isPasswordField()) |
+ return -1; // need to return something distinct from 0 |
+ |
+ return text().length(); |
+} |
+ |
+PassRefPtr<Range> AccessibilityRenderObject::ariaSelectedTextDOMRange() const |
+{ |
+ Node* node = m_renderer->element(); |
+ if (!node) |
+ return 0; |
+ |
+ RefPtr<Range> currentSelectionRange = selection().toRange(); |
+ if (!currentSelectionRange) |
+ return 0; |
+ |
+ ExceptionCode ec = 0; |
+ if (!currentSelectionRange->intersectsNode(node, ec)) |
+ return Range::create(currentSelectionRange->ownerDocument()); |
+ |
+ RefPtr<Range> ariaRange = rangeOfContents(node); |
+ Position startPosition, endPosition; |
+ |
+ // Find intersection of currentSelectionRange and ariaRange |
+ if (ariaRange->startOffset() > currentSelectionRange->startOffset()) |
+ startPosition = ariaRange->startPosition(); |
+ else |
+ startPosition = currentSelectionRange->startPosition(); |
+ |
+ if (ariaRange->endOffset() < currentSelectionRange->endOffset()) |
+ endPosition = ariaRange->endPosition(); |
+ else |
+ endPosition = currentSelectionRange->endPosition(); |
+ |
+ return Range::create(ariaRange->ownerDocument(), startPosition, endPosition); |
+} |
+ |
+String AccessibilityRenderObject::selectedText() const |
+{ |
+ ASSERT(isTextControl()); |
+ |
+ if (isPasswordField()) |
+ return String(); // need to return something distinct from empty string |
+ |
+ if (isNativeTextControl()) { |
+ RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); |
+ return textControl->text().substring(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart()); |
+ } |
+ |
+ if (ariaRoleAttribute() == UnknownRole) |
+ return String(); |
+ |
+ RefPtr<Range> ariaRange = ariaSelectedTextDOMRange(); |
+ if (!ariaRange) |
+ return String(); |
+ return ariaRange->text(); |
+} |
+ |
+const AtomicString& AccessibilityRenderObject::accessKey() const |
+{ |
+ Node* node = m_renderer->element(); |
+ if (!node) |
+ return nullAtom; |
+ if (!node->isElementNode()) |
+ return nullAtom; |
+ return static_cast<Element*>(node)->getAttribute(accesskeyAttr); |
+} |
+ |
+Selection AccessibilityRenderObject::selection() const |
+{ |
+ return m_renderer->document()->frame()->selection()->selection(); |
+} |
+ |
+PlainTextRange AccessibilityRenderObject::selectedTextRange() const |
+{ |
+ ASSERT(isTextControl()); |
+ |
+ if (isPasswordField()) |
+ return PlainTextRange(); |
+ |
+ AccessibilityRole ariaRole = ariaRoleAttribute(); |
+ if (isNativeTextControl() && ariaRole == UnknownRole) { |
+ RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); |
+ return PlainTextRange(textControl->selectionStart(), textControl->selectionEnd() - textControl->selectionStart()); |
+ } |
+ |
+ if (ariaRole == UnknownRole) |
+ return PlainTextRange(); |
+ |
+ RefPtr<Range> ariaRange = ariaSelectedTextDOMRange(); |
+ if (!ariaRange) |
+ return PlainTextRange(); |
+ return PlainTextRange(ariaRange->startOffset(), ariaRange->endOffset()); |
+} |
+ |
+void AccessibilityRenderObject::setSelectedTextRange(const PlainTextRange& range) |
+{ |
+ if (isNativeTextControl()) { |
+ RenderTextControl* textControl = static_cast<RenderTextControl*>(m_renderer); |
+ textControl->setSelectionRange(range.start, range.start + range.length); |
+ return; |
+ } |
+ |
+ Document* document = m_renderer->document(); |
+ if (!document) |
+ return; |
+ Frame* frame = document->frame(); |
+ if (!frame) |
+ return; |
+ Node* node = m_renderer->element(); |
+ frame->selection()->setSelection(Selection(Position(node, range.start), |
+ Position(node, range.start + range.length), DOWNSTREAM)); |
+} |
+ |
+KURL AccessibilityRenderObject::url() const |
+{ |
+ if (isAnchor() && m_renderer->element()->hasTagName(aTag)) { |
+ if (HTMLAnchorElement* anchor = static_cast<HTMLAnchorElement*>(anchorElement())) |
+ return anchor->href(); |
+ } |
+ |
+ if (isWebArea()) |
+ return m_renderer->document()->url(); |
+ |
+ if (isImage() && m_renderer->element() && m_renderer->element()->hasTagName(imgTag)) |
+ return static_cast<HTMLImageElement*>(m_renderer->element())->src(); |
+ |
+ if (isInputImage()) |
+ return static_cast<HTMLInputElement*>(m_renderer->element())->src(); |
+ |
+ return KURL(); |
+} |
+ |
+bool AccessibilityRenderObject::isVisited() const |
+{ |
+ return m_renderer->style()->pseudoState() == PseudoVisited; |
+} |
+ |
+bool AccessibilityRenderObject::isSelected() const |
+{ |
+ if (!m_renderer) |
+ return false; |
+ |
+ Node* node = m_renderer->node(); |
+ if (!node) |
+ return false; |
+ |
+ return false; |
+} |
+ |
+bool AccessibilityRenderObject::isFocused() const |
+{ |
+ if (!m_renderer) |
+ return false; |
+ |
+ Document* document = m_renderer->document(); |
+ if (!document) |
+ return false; |
+ |
+ Node* focusedNode = document->focusedNode(); |
+ if (!focusedNode) |
+ return false; |
+ |
+ // A web area is represented by the Document node in the DOM tree, which isn't focusable. |
+ // Check instead if the frame's selection controller is focused |
+ if (focusedNode == m_renderer->element() || |
+ (roleValue() == WebAreaRole && document->frame()->selection()->isFocusedAndActive())) |
+ return true; |
+ |
+ return false; |
+} |
+ |
+void AccessibilityRenderObject::setFocused(bool on) |
+{ |
+ if (!canSetFocusAttribute()) |
+ return; |
+ |
+ if (!on) |
+ m_renderer->document()->setFocusedNode(0); |
+ else { |
+ if (m_renderer->element()->isElementNode()) |
+ static_cast<Element*>(m_renderer->element())->focus(); |
+ else |
+ m_renderer->document()->setFocusedNode(m_renderer->element()); |
+ } |
+} |
+ |
+void AccessibilityRenderObject::setValue(const String& string) |
+{ |
+ // FIXME: Do we want to do anything here for ARIA textboxes? |
+ if (m_renderer->isTextField()) { |
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(m_renderer->element()); |
+ input->setValue(string); |
+ } else if (m_renderer->isTextArea()) { |
+ HTMLTextAreaElement* textArea = static_cast<HTMLTextAreaElement*>(m_renderer->element()); |
+ textArea->setValue(string); |
+ } |
+} |
+ |
+bool AccessibilityRenderObject::isEnabled() const |
+{ |
+ ASSERT(m_renderer); |
+ if (!m_renderer->element() || !m_renderer->element()->isElementNode()) |
+ return true; |
+ |
+ FormControlElement* formControlElement = toFormControlElement(static_cast<Element*>(m_renderer->element())); |
+ if (!formControlElement) |
+ return true; |
+ |
+ return formControlElement->isEnabled(); |
+} |
+ |
+RenderView* AccessibilityRenderObject::topRenderer() const |
+{ |
+ return m_renderer->document()->topDocument()->renderView(); |
+} |
+ |
+Document* AccessibilityRenderObject::document() const |
+{ |
+ return m_renderer->document(); |
+} |
+ |
+FrameView* AccessibilityRenderObject::topDocumentFrameView() const |
+{ |
+ return topRenderer()->view()->frameView(); |
+} |
+ |
+Widget* AccessibilityRenderObject::widget() const |
+{ |
+ if (!m_renderer->isWidget()) |
+ return 0; |
+ |
+ return static_cast<RenderWidget*>(m_renderer)->widget(); |
+} |
+ |
+AXObjectCache* AccessibilityRenderObject::axObjectCache() const |
+{ |
+ return m_renderer->document()->axObjectCache(); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::accessibilityParentForImageMap(HTMLMapElement* map) const |
+{ |
+ // find an image that is using this map |
+ if (!m_renderer || !map) |
+ return 0; |
+ |
+ RefPtr<HTMLCollection> coll = m_renderer->document()->images(); |
+ for (Node* curr = coll->firstItem(); curr; curr = coll->nextItem()) { |
+ RenderObject* obj = curr->renderer(); |
+ if (!obj || !curr->hasTagName(imgTag)) |
+ continue; |
+ |
+ // The HTMLImageElement's useMap() value includes the '#' symbol at the beginning, |
+ // which has to be stripped off |
+ if (static_cast<HTMLImageElement*>(curr)->useMap().substring(1) == map->getName()) |
+ return axObjectCache()->get(obj); |
+ } |
+ |
+ return 0; |
+} |
+ |
+void AccessibilityRenderObject::getDocumentLinks(AccessibilityChildrenVector& result) |
+{ |
+ Document* document = m_renderer->document(); |
+ RefPtr<HTMLCollection> coll = document->links(); |
+ Node* curr = coll->firstItem(); |
+ while (curr) { |
+ RenderObject* obj = curr->renderer(); |
+ if (obj) { |
+ RefPtr<AccessibilityObject> axobj = document->axObjectCache()->get(obj); |
+ ASSERT(axobj); |
+ ASSERT(axobj->roleValue() == WebCoreLinkRole); |
+ if (!axobj->accessibilityIsIgnored()) |
+ result.append(axobj); |
+ } else { |
+ Node* parent = curr->parent(); |
+ if (parent && curr->hasTagName(areaTag) && parent->hasTagName(mapTag)) { |
+ AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(axObjectCache()->get(ImageMapLinkRole)); |
+ areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(curr)); |
+ areaObject->setHTMLMapElement(static_cast<HTMLMapElement*>(parent)); |
+ areaObject->setParent(accessibilityParentForImageMap(static_cast<HTMLMapElement*>(parent))); |
+ |
+ result.append(areaObject); |
+ } |
+ } |
+ curr = coll->nextItem(); |
+ } |
+} |
+ |
+FrameView* AccessibilityRenderObject::documentFrameView() const |
+{ |
+ if (!m_renderer || !m_renderer->document()) |
+ return 0; |
+ |
+ // this is the RenderObject's Document's Frame's FrameView |
+ return m_renderer->document()->view(); |
+} |
+ |
+Widget* AccessibilityRenderObject::widgetForAttachmentView() const |
+{ |
+ if (!isAttachment()) |
+ return 0; |
+ return static_cast<RenderWidget*>(m_renderer)->widget(); |
+} |
+ |
+FrameView* AccessibilityRenderObject::frameViewIfRenderView() const |
+{ |
+ if (!m_renderer->isRenderView()) |
+ return 0; |
+ // this is the RenderObject's Document's renderer's FrameView |
+ return m_renderer->view()->frameView(); |
+} |
+ |
+// This function is like a cross-platform version of - (WebCoreTextMarkerRange*)textMarkerRange. It returns |
+// a Range that we can convert to a WebCoreTextMarkerRange in the Obj-C file |
+VisiblePositionRange AccessibilityRenderObject::visiblePositionRange() const |
+{ |
+ if (!m_renderer) |
+ return VisiblePositionRange(); |
+ |
+ // construct VisiblePositions for start and end |
+ Node* node = m_renderer->element(); |
+ if (!node) |
+ return VisiblePositionRange(); |
+ |
+ VisiblePosition startPos = VisiblePosition(node, 0, VP_DEFAULT_AFFINITY); |
+ VisiblePosition endPos = VisiblePosition(node, maxDeepOffset(node), VP_DEFAULT_AFFINITY); |
+ |
+ // the VisiblePositions are equal for nodes like buttons, so adjust for that |
+ if (startPos == endPos) { |
+ endPos = endPos.next(); |
+ if (endPos.isNull()) |
+ endPos = startPos; |
+ } |
+ |
+ return VisiblePositionRange(startPos, endPos); |
+} |
+ |
+VisiblePositionRange AccessibilityRenderObject::visiblePositionRangeForLine(unsigned lineCount) const |
+{ |
+ if (lineCount == 0 || !m_renderer) |
+ return VisiblePositionRange(); |
+ |
+ // 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->document()->renderer()->positionForCoordinates(0, 0); |
+ VisiblePosition savedVisiblePos; |
+ while (--lineCount != 0) { |
+ savedVisiblePos = visiblePos; |
+ visiblePos = nextLinePosition(visiblePos, 0); |
+ if (visiblePos.isNull() || visiblePos == savedVisiblePos) |
+ return VisiblePositionRange(); |
+ } |
+ |
+ // make a caret selection for the marker position, then extend it to the line |
+ // NOTE: ignores results of sel.modify because it returns false when |
+ // starting at an empty line. The resulting selection in that case |
+ // will be a caret at visiblePos. |
+ SelectionController selection; |
+ selection.setSelection(Selection(visiblePos)); |
+ selection.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary); |
+ |
+ return VisiblePositionRange(selection.selection().visibleStart(), selection.selection().visibleEnd()); |
+} |
+ |
+VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(int index) const |
+{ |
+ if (!m_renderer) |
+ return VisiblePosition(); |
+ |
+ if (isNativeTextControl()) |
+ return static_cast<RenderTextControl*>(m_renderer)->visiblePositionForIndex(index); |
+ |
+ if (!isTextControl() && !m_renderer->isText()) |
+ return VisiblePosition(); |
+ |
+ Node* node = m_renderer->node(); |
+ if (!node) |
+ return VisiblePosition(); |
+ |
+ if (index <= 0) |
+ return VisiblePosition(node, 0, DOWNSTREAM); |
+ |
+ ExceptionCode ec = 0; |
+ RefPtr<Range> range = Range::create(m_renderer->document()); |
+ range->selectNodeContents(node, ec); |
+ CharacterIterator it(range.get()); |
+ it.advance(index - 1); |
+ return VisiblePosition(it.range()->endContainer(ec), it.range()->endOffset(ec), UPSTREAM); |
+} |
+ |
+int AccessibilityRenderObject::indexForVisiblePosition(const VisiblePosition& pos) const |
+{ |
+ if (isNativeTextControl()) |
+ return static_cast<RenderTextControl*>(m_renderer)->indexForVisiblePosition(pos); |
+ |
+ if (!isTextControl()) |
+ return 0; |
+ |
+ Node* node = m_renderer->node(); |
+ if (!node) |
+ return 0; |
+ |
+ Position indexPosition = pos.deepEquivalent(); |
+ if (!indexPosition.node() || indexPosition.node()->rootEditableElement() != node) |
+ return 0; |
+ |
+ ExceptionCode ec = 0; |
+ RefPtr<Range> range = Range::create(m_renderer->document()); |
+ range->setStart(node, 0, ec); |
+ range->setEnd(indexPosition.node(), indexPosition.offset(), ec); |
+ return TextIterator::rangeLength(range.get()); |
+} |
+ |
+IntRect AccessibilityRenderObject::boundsForVisiblePositionRange(const VisiblePositionRange& visiblePositionRange) const |
+{ |
+ if (visiblePositionRange.isNull()) |
+ return IntRect(); |
+ |
+ // Create a mutable VisiblePositionRange. |
+ VisiblePositionRange range(visiblePositionRange); |
+ IntRect rect1 = range.start.absoluteCaretBounds(); |
+ IntRect rect2 = range.end.absoluteCaretBounds(); |
+ |
+ // 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()) { |
+ VisiblePosition endOfFirstLine = endOfLine(range.start); |
+ if (range.start == endOfFirstLine) { |
+ range.start.setAffinity(DOWNSTREAM); |
+ rect1 = range.start.absoluteCaretBounds(); |
+ } |
+ if (range.end == endOfFirstLine) { |
+ range.end.setAffinity(UPSTREAM); |
+ rect2 = range.end.absoluteCaretBounds(); |
+ } |
+ } |
+ |
+ IntRect ourrect = rect1; |
+ ourrect.unite(rect2); |
+ |
+ // if the rectangle spans lines and contains multiple text chars, use the range's bounding box intead |
+ if (rect1.bottom() != rect2.bottom()) { |
+ RefPtr<Range> dataRange = makeRange(range.start, range.end); |
+ IntRect boundingBox = dataRange->boundingBox(); |
+ String rangeString = plainText(dataRange.get()); |
+ if (rangeString.length() > 1 && !boundingBox.isEmpty()) |
+ ourrect = boundingBox; |
+ } |
+ |
+#if PLATFORM(MAC) |
+ return m_renderer->document()->view()->contentsToScreen(ourrect); |
+#else |
+ return ourrect; |
+#endif |
+} |
+ |
+void AccessibilityRenderObject::setSelectedVisiblePositionRange(const VisiblePositionRange& range) const |
+{ |
+ if (range.start.isNull() || range.end.isNull()) |
+ 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) { |
+ m_renderer->document()->frame()->selection()->moveTo(range.start, true); |
+ } |
+ else { |
+ Selection newSelection = Selection(range.start, range.end); |
+ m_renderer->document()->frame()->selection()->setSelection(newSelection); |
+ } |
+} |
+ |
+VisiblePosition AccessibilityRenderObject::visiblePositionForPoint(const IntPoint& point) const |
+{ |
+ // convert absolute point to view coordinates |
+ FrameView* frameView = m_renderer->document()->topDocument()->renderer()->view()->frameView(); |
+ RenderView* renderView = topRenderer(); |
+ Node* innerNode = 0; |
+ |
+ // locate the node containing the point |
+ IntPoint pointResult; |
+ while (1) { |
+ IntPoint ourpoint; |
+#if PLATFORM(MAC) |
+ ourpoint = frameView->screenToContents(point); |
+#else |
+ ourpoint = point; |
+#endif |
+ HitTestRequest request(HitTestRequest::ReadOnly | |
+ HitTestRequest::Active); |
+ HitTestResult result(ourpoint); |
+ renderView->layer()->hitTest(request, result); |
+ innerNode = result.innerNode(); |
+ if (!innerNode || !innerNode->renderer()) |
+ return VisiblePosition(); |
+ |
+ pointResult = result.localPoint(); |
+ |
+ // done if hit something other than a widget |
+ RenderObject* renderer = innerNode->renderer(); |
+ if (!renderer->isWidget()) |
+ break; |
+ |
+ // descend into widget (FRAME, IFRAME, OBJECT...) |
+ Widget* widget = static_cast<RenderWidget*>(renderer)->widget(); |
+ if (!widget || !widget->isFrameView()) |
+ break; |
+ Frame* frame = static_cast<FrameView*>(widget)->frame(); |
+ if (!frame) |
+ break; |
+ Document* document = frame->document(); |
+ if (!document) |
+ break; |
+ renderView = document->renderView(); |
+ frameView = static_cast<FrameView*>(widget); |
+ } |
+ |
+ return innerNode->renderer()->positionForPoint(pointResult); |
+} |
+ |
+// NOTE: Consider providing this utility method as AX API |
+VisiblePosition AccessibilityRenderObject::visiblePositionForIndex(unsigned indexValue, bool lastIndexOK) const |
+{ |
+ if (!isTextControl()) |
+ return VisiblePosition(); |
+ |
+ // lastIndexOK specifies whether the position after the last character is acceptable |
+ if (indexValue >= text().length()) { |
+ if (!lastIndexOK || indexValue > text().length()) |
+ return VisiblePosition(); |
+ } |
+ VisiblePosition position = visiblePositionForIndex(indexValue); |
+ position.setAffinity(DOWNSTREAM); |
+ return position; |
+} |
+ |
+// NOTE: Consider providing this utility method as AX API |
+int AccessibilityRenderObject::index(const VisiblePosition& position) const |
+{ |
+ if (!isTextControl()) |
+ return -1; |
+ |
+ Node* node = position.deepEquivalent().node(); |
+ if (!node) |
+ return -1; |
+ |
+ for (RenderObject* renderer = node->renderer(); renderer && renderer->element(); renderer = renderer->parent()) { |
+ if (renderer == m_renderer) |
+ return indexForVisiblePosition(position); |
+ } |
+ |
+ return -1; |
+} |
+ |
+// Given a line number, the range of characters of the text associated with this accessibility |
+// object that contains the line number. |
+PlainTextRange AccessibilityRenderObject::doAXRangeForLine(unsigned lineNumber) const |
+{ |
+ if (!isTextControl()) |
+ return PlainTextRange(); |
+ |
+ // iterate to the specified line |
+ VisiblePosition visiblePos = visiblePositionForIndex(0); |
+ VisiblePosition savedVisiblePos; |
+ for (unsigned lineCount = lineNumber; lineCount != 0; lineCount -= 1) { |
+ savedVisiblePos = visiblePos; |
+ visiblePos = nextLinePosition(visiblePos, 0); |
+ if (visiblePos.isNull() || visiblePos == savedVisiblePos) |
+ return PlainTextRange(); |
+ } |
+ |
+ // make a caret selection for the marker position, then extend it to the line |
+ // NOTE: ignores results of selection.modify because it returns false when |
+ // starting at an empty line. The resulting selection in that case |
+ // will be a caret at visiblePos. |
+ SelectionController selection; |
+ selection.setSelection(Selection(visiblePos)); |
+ selection.modify(SelectionController::EXTEND, SelectionController::LEFT, LineBoundary); |
+ selection.modify(SelectionController::EXTEND, SelectionController::RIGHT, LineBoundary); |
+ |
+ // calculate the indices for the selection start and end |
+ VisiblePosition startPosition = selection.selection().visibleStart(); |
+ VisiblePosition endPosition = selection.selection().visibleEnd(); |
+ int index1 = indexForVisiblePosition(startPosition); |
+ int index2 = indexForVisiblePosition(endPosition); |
+ |
+ // add one to the end index for a line break not caused by soft line wrap (to match AppKit) |
+ if (endPosition.affinity() == DOWNSTREAM && endPosition.next().isNotNull()) |
+ index2 += 1; |
+ |
+ // return nil rather than an zero-length range (to match AppKit) |
+ if (index1 == index2) |
+ return PlainTextRange(); |
+ |
+ return PlainTextRange(index1, index2 - index1); |
+} |
+ |
+// The composed character range in the text associated with this accessibility object that |
+// is specified by the given index value. This parameterized attribute returns the complete |
+// range of characters (including surrogate pairs of multi-byte glyphs) at the given index. |
+PlainTextRange AccessibilityRenderObject::doAXRangeForIndex(unsigned index) const |
+{ |
+ if (!isTextControl()) |
+ return PlainTextRange(); |
+ |
+ String elementText = text(); |
+ if (!elementText.length() || index > elementText.length() - 1) |
+ return PlainTextRange(); |
+ |
+ return PlainTextRange(index, 1); |
+} |
+ |
+// A substring of the text associated with this accessibility object that is |
+// specified by the given character range. |
+String AccessibilityRenderObject::doAXStringForRange(const PlainTextRange& range) const |
+{ |
+ if (isPasswordField()) |
+ return String(); |
+ |
+ if (range.length == 0) |
+ return ""; |
+ |
+ if (!isTextControl()) |
+ return String(); |
+ |
+ String elementText = text(); |
+ if (range.start + range.length > elementText.length()) |
+ return String(); |
+ |
+ return elementText.substring(range.start, range.length); |
+} |
+ |
+// The bounding rectangle of the text associated with this accessibility object that is |
+// specified by the given range. This is the bounding rectangle a sighted user would see |
+// on the display screen, in pixels. |
+IntRect AccessibilityRenderObject::doAXBoundsForRange(const PlainTextRange& range) const |
+{ |
+ if (isTextControl()) |
+ return boundsForVisiblePositionRange(visiblePositionRangeForRange(range)); |
+ return IntRect(); |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::doAccessibilityHitTest(const IntPoint& point) const |
+{ |
+ if (!m_renderer || !m_renderer->hasLayer()) |
+ return 0; |
+ |
+ RenderLayer* layer = toRenderBox(m_renderer)->layer(); |
+ |
+ HitTestRequest request(HitTestRequest::ReadOnly | |
+ HitTestRequest::Active); |
+ HitTestResult hitTestResult = HitTestResult(point); |
+ layer->hitTest(request, hitTestResult); |
+ if (!hitTestResult.innerNode()) |
+ return 0; |
+ Node* node = hitTestResult.innerNode()->shadowAncestorNode(); |
+ RenderObject* obj = node->renderer(); |
+ if (!obj) |
+ return 0; |
+ |
+ AccessibilityObject *result = obj->document()->axObjectCache()->get(obj); |
+ |
+ if (obj->isListBox()) |
+ return static_cast<AccessibilityListBox*>(result)->doAccessibilityHitTest(point); |
+ |
+ if (result->accessibilityIsIgnored()) |
+ result = result->parentObjectUnignored(); |
+ |
+ return result; |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::focusedUIElement() const |
+{ |
+ // get the focused node in the page |
+ Page* page = m_renderer->document()->page(); |
+ if (!page) |
+ return 0; |
+ |
+ Document* focusedDocument = page->focusController()->focusedOrMainFrame()->document(); |
+ Node* focusedNode = focusedDocument->focusedNode(); |
+ if (!focusedNode) |
+ focusedNode = focusedDocument; |
+ |
+ RenderObject* focusedNodeRenderer = focusedNode->renderer(); |
+ if (!focusedNodeRenderer) |
+ return 0; |
+ |
+ AccessibilityObject* obj = focusedNodeRenderer->document()->axObjectCache()->get(focusedNodeRenderer); |
+ |
+ if (obj->shouldFocusActiveDescendant()) { |
+ if (AccessibilityObject* descendant = obj->activeDescendant()) |
+ obj = descendant; |
+ } |
+ |
+ // the HTML element, for example, is focusable but has an AX object that is ignored |
+ if (obj->accessibilityIsIgnored()) |
+ obj = obj->parentObjectUnignored(); |
+ |
+ return obj; |
+} |
+ |
+bool AccessibilityRenderObject::shouldFocusActiveDescendant() const |
+{ |
+ switch (ariaRoleAttribute()) { |
+ case GroupRole: |
+ case ComboBoxRole: |
+ case ListBoxRole: |
+ case MenuRole: |
+ case MenuBarRole: |
+ case RadioGroupRole: |
+ case RowRole: |
+ case PopUpButtonRole: |
+ case ProgressIndicatorRole: |
+ case ToolbarRole: |
+ case OutlineRole: |
+ /* FIXME: replace these with actual roles when they are added to AccessibilityRole |
+ composite |
+ alert |
+ alertdialog |
+ grid |
+ status |
+ timer |
+ tree |
+ */ |
+ return true; |
+ default: |
+ return false; |
+ } |
+} |
+ |
+AccessibilityObject* AccessibilityRenderObject::activeDescendant() const |
+{ |
+ if (renderer()->element() && !renderer()->element()->isElementNode()) |
+ return 0; |
+ Element* element = static_cast<Element*>(renderer()->element()); |
+ |
+ String activeDescendantAttrStr = element->getAttribute(aria_activedescendantAttr).string(); |
+ if (activeDescendantAttrStr.isNull() || activeDescendantAttrStr.isEmpty()) |
+ return 0; |
+ |
+ Element* target = renderer()->document()->getElementById(activeDescendantAttrStr); |
+ if (!target) |
+ return 0; |
+ |
+ AccessibilityObject* obj = renderer()->document()->axObjectCache()->get(target->renderer()); |
+ if (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; |
+} |
+ |
+ |
+void AccessibilityRenderObject::handleActiveDescendantChanged() |
+{ |
+ Element* element = static_cast<Element*>(renderer()->element()); |
+ if (!element) |
+ return; |
+ Document* doc = renderer()->document(); |
+ if (!doc->frame()->selection()->isFocusedAndActive() || doc->focusedNode() != element) |
+ return; |
+ AccessibilityRenderObject* activedescendant = static_cast<AccessibilityRenderObject*>(activeDescendant()); |
+ |
+ if (activedescendant && shouldFocusActiveDescendant()) |
+ doc->axObjectCache()->postNotificationToElement(activedescendant->renderer(), "AXFocusedUIElementChanged"); |
+} |
+ |
+ |
+AccessibilityObject* AccessibilityRenderObject::observableObject() const |
+{ |
+ for (RenderObject* renderer = m_renderer; renderer && renderer->element(); renderer = renderer->parent()) { |
+ if (renderer->isTextField() || renderer->isTextArea()) |
+ return renderer->document()->axObjectCache()->get(renderer); |
+ } |
+ |
+ return 0; |
+} |
+ |
+typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap; |
+ |
+static const ARIARoleMap& createARIARoleMap() |
+{ |
+ struct RoleEntry { |
+ String ariaRole; |
+ AccessibilityRole webcoreRole; |
+ }; |
+ |
+ const RoleEntry roles[] = { |
+ { "button", ButtonRole }, |
+ { "checkbox", CheckBoxRole }, |
+ { "group", GroupRole }, |
+ { "heading", HeadingRole }, |
+ { "img", ImageRole }, |
+ { "link", WebCoreLinkRole }, |
+ { "listbox", ListBoxRole }, |
+ // "option" isn't here because it may map to different roles depending on the parent element's role |
+ { "menu", MenuRole }, |
+ { "menubar", GroupRole }, |
+ // "menuitem" isn't here because it may map to different roles depending on the parent element's role |
+ { "menuitemcheckbox", MenuItemRole }, |
+ { "menuitemradio", MenuItemRole }, |
+ { "progressbar", ProgressIndicatorRole }, |
+ { "radio", RadioButtonRole }, |
+ { "range", SliderRole }, |
+ { "slider", SliderRole }, |
+ { "spinbutton", ProgressIndicatorRole }, |
+ { "textbox", TextAreaRole } |
+ }; |
+ ARIARoleMap& roleMap = *new ARIARoleMap; |
+ |
+ const unsigned numRoles = sizeof(roles) / sizeof(roles[0]); |
+ for (unsigned i = 0; i < numRoles; ++i) |
+ roleMap.set(roles[i].ariaRole, roles[i].webcoreRole); |
+ return roleMap; |
+} |
+ |
+static AccessibilityRole ariaRoleToWebCoreRole(String value) |
+{ |
+ ASSERT(!value.isEmpty() && !value.isNull()); |
+ static const ARIARoleMap& roleMap = createARIARoleMap(); |
+ return roleMap.get(value); |
+} |
+ |
+AccessibilityRole AccessibilityRenderObject::determineAriaRoleAttribute() const |
+{ |
+ String ariaRole = getAttribute(roleAttr).string(); |
+ if (ariaRole.isNull() || ariaRole.isEmpty()) |
+ return UnknownRole; |
+ |
+ AccessibilityRole role = ariaRoleToWebCoreRole(ariaRole); |
+ if (role) |
+ return role; |
+ // selects and listboxes both have options as child roles, but they map to different roles within WebCore |
+ if (equalIgnoringCase(ariaRole,"option")) { |
+ if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole) |
+ return MenuItemRole; |
+ if (parentObjectUnignored()->ariaRoleAttribute() == ListBoxRole) |
+ return ListBoxOptionRole; |
+ } |
+ // an aria "menuitem" may map to MenuButton or MenuItem depending on its parent |
+ if (equalIgnoringCase(ariaRole,"menuitem")) { |
+ if (parentObjectUnignored()->ariaRoleAttribute() == GroupRole) |
+ return MenuButtonRole; |
+ if (parentObjectUnignored()->ariaRoleAttribute() == MenuRole) |
+ return MenuItemRole; |
+ } |
+ |
+ return UnknownRole; |
+} |
+ |
+void AccessibilityRenderObject::setAriaRole() |
+{ |
+ m_ariaRole = determineAriaRoleAttribute(); |
+} |
+ |
+AccessibilityRole AccessibilityRenderObject::ariaRoleAttribute() const |
+{ |
+ return m_ariaRole; |
+} |
+ |
+AccessibilityRole AccessibilityRenderObject::roleValue() const |
+{ |
+ if (!m_renderer) |
+ return UnknownRole; |
+ |
+ Node* node = m_renderer->element(); |
+ AccessibilityRole ariaRole = ariaRoleAttribute(); |
+ if (ariaRole != UnknownRole) |
+ return ariaRole; |
+ |
+ if (node && node->isLink()) { |
+ if (m_renderer->isImage()) |
+ return ImageMapRole; |
+ return WebCoreLinkRole; |
+ } |
+ if (m_renderer->isListMarker()) |
+ return ListMarkerRole; |
+ if (node && node->hasTagName(buttonTag)) |
+ return ButtonRole; |
+ if (m_renderer->isText()) |
+ return StaticTextRole; |
+ if (m_renderer->isImage()) { |
+ if (node && node->hasTagName(inputTag)) |
+ return ButtonRole; |
+ return ImageRole; |
+ } |
+ if (m_renderer->isRenderView()) |
+ return WebAreaRole; |
+ |
+ if (m_renderer->isTextField()) |
+ return TextFieldRole; |
+ |
+ if (m_renderer->isTextArea()) |
+ return TextAreaRole; |
+ |
+ if (node && node->hasTagName(inputTag)) { |
+ HTMLInputElement* input = static_cast<HTMLInputElement*>(node); |
+ if (input->inputType() == HTMLInputElement::CHECKBOX) |
+ return CheckBoxRole; |
+ if (input->inputType() == HTMLInputElement::RADIO) |
+ return RadioButtonRole; |
+ if (input->isTextButton()) |
+ return ButtonRole; |
+ } |
+ |
+ if (node && node->hasTagName(buttonTag)) |
+ return ButtonRole; |
+ |
+ if (isFileUploadButton()) |
+ return ButtonRole; |
+ |
+ if (m_renderer->isMenuList()) |
+ return PopUpButtonRole; |
+ |
+ if (headingLevel(m_renderer->element()) != 0) |
+ return HeadingRole; |
+ |
+ if (node && node->hasTagName(ddTag)) |
+ return DefinitionListDefinitionRole; |
+ |
+ if (node && node->hasTagName(dtTag)) |
+ return DefinitionListTermRole; |
+ |
+ if (m_renderer->isBlockFlow() || (node && node->hasTagName(labelTag))) |
+ return GroupRole; |
+ |
+ return UnknownRole; |
+} |
+ |
+bool AccessibilityRenderObject::isPresentationalChildOfAriaRole() const |
+{ |
+ // Walk the parent chain looking for a parent that has presentational children |
+ AccessibilityObject* parent; |
+ for (parent = parentObject(); parent && !parent->ariaRoleHasPresentationalChildren(); parent = parent->parentObject()) |
+ ; |
+ return parent; |
+} |
+ |
+bool AccessibilityRenderObject::ariaRoleHasPresentationalChildren() const |
+{ |
+ switch (m_ariaRole) { |
+ case ButtonRole: |
+ case SliderRole: |
+ case ImageRole: |
+ case ProgressIndicatorRole: |
+ //case SeparatorRole: |
+ return true; |
+ default: |
+ return false; |
+ } |
+} |
+ |
+bool AccessibilityRenderObject::canSetFocusAttribute() const |
+{ |
+ ASSERT(m_renderer); |
+ |
+ // NOTE: It would be more accurate to ask the document whether setFocusedNode() would |
+ // do anything. For example, it setFocusedNode() will do nothing if the current focused |
+ // node will not relinquish the focus. |
+ if (!m_renderer->element() || !m_renderer->element()->isElementNode()) |
+ return false; |
+ |
+ FormControlElement* formControlElement = toFormControlElement(static_cast<Element*>(m_renderer->element())); |
+ if (formControlElement && !formControlElement->isEnabled()) |
+ return false; |
+ |
+ switch (roleValue()) { |
+ case WebCoreLinkRole: |
+ case ImageMapLinkRole: |
+ case TextFieldRole: |
+ case TextAreaRole: |
+ case ButtonRole: |
+ case PopUpButtonRole: |
+ case CheckBoxRole: |
+ case RadioButtonRole: |
+ return true; |
+ default: |
+ return false; |
+ } |
+} |
+ |
+bool AccessibilityRenderObject::canSetValueAttribute() const |
+{ |
+ if (isWebArea()) |
+ return !isReadOnly(); |
+ |
+ return isTextControl() || isProgressIndicator() || isSlider(); |
+} |
+ |
+bool AccessibilityRenderObject::canSetTextRangeAttributes() const |
+{ |
+ return isTextControl(); |
+} |
+ |
+void AccessibilityRenderObject::childrenChanged() |
+{ |
+ // this method is meant as a quick way of marking dirty |
+ // a portion of the accessibility tree |
+ |
+ markChildrenDirty(); |
+ |
+ // this object may not be accessible (and thus may not appear |
+ // in the hierarchy), which means we need to go up the parent |
+ // chain and mark the parent's dirty. Ideally, we would want |
+ // to only access the next object that is not ignored, but |
+ // asking an element if it's ignored can lead to an examination of the |
+ // render tree which is dangerous. |
+ for (AccessibilityObject* parent = parentObject(); parent; parent = parent->parentObject()) { |
+ if (parent->isAccessibilityRenderObject()) |
+ static_cast<AccessibilityRenderObject *>(parent)->markChildrenDirty(); |
+ } |
+} |
+ |
+bool AccessibilityRenderObject::canHaveChildren() const |
+{ |
+ if (!m_renderer) |
+ return false; |
+ |
+ // Elements that should not have children |
+ switch (roleValue()) { |
+ case ImageRole: |
+ case ButtonRole: |
+ case PopUpButtonRole: |
+ case CheckBoxRole: |
+ case RadioButtonRole: |
+ return false; |
+ default: |
+ return true; |
+ } |
+} |
+ |
+const AccessibilityObject::AccessibilityChildrenVector& AccessibilityRenderObject::children() |
+{ |
+ if (m_childrenDirty) { |
+ clearChildren(); |
+ m_childrenDirty = false; |
+ } |
+ |
+ if (!m_haveChildren) |
+ addChildren(); |
+ return m_children; |
+} |
+ |
+void AccessibilityRenderObject::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); |
+ |
+ // nothing to add if there is no RenderObject |
+ if (!m_renderer) |
+ return; |
+ |
+ m_haveChildren = true; |
+ |
+ if (!canHaveChildren()) |
+ return; |
+ |
+ // add all unignored acc children |
+ for (RefPtr<AccessibilityObject> obj = firstChild(); obj; obj = obj->nextSibling()) { |
+ if (obj->accessibilityIsIgnored()) { |
+ if (!obj->hasChildren()) |
+ obj->addChildren(); |
+ AccessibilityChildrenVector children = obj->children(); |
+ unsigned length = children.size(); |
+ for (unsigned i = 0; i < length; ++i) |
+ m_children.append(children[i]); |
+ } else |
+ m_children.append(obj); |
+ } |
+ |
+ // for a RenderImage, add the <area> elements as individual accessibility objects |
+ if (m_renderer->isRenderImage()) { |
+ HTMLMapElement* map = toRenderImage(m_renderer)->imageMap(); |
+ if (map) { |
+ for (Node* current = map->firstChild(); current; current = current->traverseNextNode(map)) { |
+ |
+ // add an <area> element for this child if it has a link |
+ if (current->isLink()) { |
+ AccessibilityImageMapLink* areaObject = static_cast<AccessibilityImageMapLink*>(m_renderer->document()->axObjectCache()->get(ImageMapLinkRole)); |
+ areaObject->setHTMLAreaElement(static_cast<HTMLAreaElement*>(current)); |
+ areaObject->setHTMLMapElement(map); |
+ areaObject->setParent(this); |
+ |
+ m_children.append(areaObject); |
+ } |
+ } |
+ } |
+ } |
+} |
+ |
+void AccessibilityRenderObject::ariaListboxSelectedChildren(AccessibilityChildrenVector& result) |
+{ |
+ AccessibilityObject* child = firstChild(); |
+ bool isMultiselectable = false; |
+ |
+ Element* element = static_cast<Element*>(renderer()->element()); |
+ if (!element || !element->isElementNode()) // do this check to ensure safety of static_cast above |
+ return; |
+ |
+ String multiselectablePropertyStr = element->getAttribute("aria-multiselectable").string(); |
+ isMultiselectable = equalIgnoringCase(multiselectablePropertyStr, "true"); |
+ |
+ while (child) { |
+ // every child should have aria-role option, and if so, check for selected attribute/state |
+ AccessibilityRole ariaRole = child->ariaRoleAttribute(); |
+ RenderObject* childRenderer = 0; |
+ if (child->isAccessibilityRenderObject()) |
+ childRenderer = static_cast<AccessibilityRenderObject*>(child)->renderer(); |
+ if (childRenderer && ariaRole == ListBoxOptionRole) { |
+ Element* childElement = static_cast<Element*>(childRenderer->element()); |
+ if (childElement && childElement->isElementNode()) { // do this check to ensure safety of static_cast above |
+ String selectedAttrString = childElement->getAttribute("aria-selected").string(); |
+ if (equalIgnoringCase(selectedAttrString, "true")) { |
+ result.append(child); |
+ if (isMultiselectable) |
+ return; |
+ } |
+ } |
+ } |
+ child = child->nextSibling(); |
+ } |
+} |
+ |
+void AccessibilityRenderObject::selectedChildren(AccessibilityChildrenVector& result) |
+{ |
+ ASSERT(result.isEmpty()); |
+ |
+ // only listboxes should be asked for their selected children. |
+ if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes |
+ ASSERT_NOT_REACHED(); |
+ return; |
+ } |
+ return ariaListboxSelectedChildren(result); |
+} |
+ |
+void AccessibilityRenderObject::ariaListboxVisibleChildren(AccessibilityChildrenVector& result) |
+{ |
+ if (!hasChildren()) |
+ addChildren(); |
+ |
+ unsigned length = m_children.size(); |
+ for (unsigned i = 0; i < length; i++) { |
+ if (!m_children[i]->isOffScreen()) |
+ result.append(m_children[i]); |
+ } |
+} |
+ |
+void AccessibilityRenderObject::visibleChildren(AccessibilityChildrenVector& result) |
+{ |
+ ASSERT(result.isEmpty()); |
+ |
+ // only listboxes are asked for their visible children. |
+ if (ariaRoleAttribute() != ListBoxRole) { // native list boxes would be AccessibilityListBoxes, so only check for aria list boxes |
+ ASSERT_NOT_REACHED(); |
+ return; |
+ } |
+ return ariaListboxVisibleChildren(result); |
+} |
+ |
+void AccessibilityRenderObject::removeAXObjectID() |
+{ |
+ if (!m_id) |
+ return; |
+#if PLATFORM(MAC) |
+ m_renderer->document()->axObjectCache()->removeAXID(this); |
+#endif |
+} |
+ |
+const String& AccessibilityRenderObject::actionVerb() const |
+{ |
+ // 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())); |
+ DEFINE_STATIC_LOCAL(const String, noAction, ()); |
+ |
+ switch (roleValue()) { |
+ case ButtonRole: |
+ return buttonAction; |
+ case TextFieldRole: |
+ case TextAreaRole: |
+ return textFieldAction; |
+ case RadioButtonRole: |
+ return radioButtonAction; |
+ case CheckBoxRole: |
+ return isChecked() ? checkedCheckBoxAction : uncheckedCheckBoxAction; |
+ case LinkRole: |
+ case WebCoreLinkRole: |
+ return linkAction; |
+ default: |
+ return noAction; |
+ } |
+} |
+ |
+void AccessibilityRenderObject::updateBackingStore() |
+{ |
+ if (!m_renderer) |
+ return; |
+ m_renderer->view()->layoutIfNeeded(); |
+} |
+ |
+} // namespace WebCore |