blob: c628d6e0f000c4eb5b238dedb91035c238c8d252 [file] [log] [blame]
/*
* 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 "modules/accessibility/AXLayoutObject.h"
#include "bindings/core/v8/ExceptionState.h"
#include "core/CSSPropertyNames.h"
#include "core/InputTypeNames.h"
#include "core/dom/ElementTraversal.h"
#include "core/dom/Range.h"
#include "core/dom/shadow/ShadowRoot.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/FrameSelection.h"
#include "core/editing/RenderedPosition.h"
#include "core/editing/TextAffinity.h"
#include "core/editing/VisibleUnits.h"
#include "core/editing/iterators/CharacterIterator.h"
#include "core/editing/iterators/TextIterator.h"
#include "core/frame/FrameOwner.h"
#include "core/frame/FrameView.h"
#include "core/frame/ImageBitmap.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/Settings.h"
#include "core/html/HTMLCanvasElement.h"
#include "core/html/HTMLFrameOwnerElement.h"
#include "core/html/HTMLImageElement.h"
#include "core/html/HTMLInputElement.h"
#include "core/html/HTMLLabelElement.h"
#include "core/html/HTMLOptionElement.h"
#include "core/html/HTMLSelectElement.h"
#include "core/html/HTMLTextAreaElement.h"
#include "core/html/HTMLVideoElement.h"
#include "core/html/ImageData.h"
#include "core/html/LabelsNodeList.h"
#include "core/html/shadow/ShadowElementNames.h"
#include "core/imagebitmap/ImageBitmapOptions.h"
#include "core/layout/HitTestResult.h"
#include "core/layout/LayoutFileUploadControl.h"
#include "core/layout/LayoutHTMLCanvas.h"
#include "core/layout/LayoutImage.h"
#include "core/layout/LayoutInline.h"
#include "core/layout/LayoutListMarker.h"
#include "core/layout/LayoutMenuList.h"
#include "core/layout/LayoutTextControl.h"
#include "core/layout/LayoutTextFragment.h"
#include "core/layout/LayoutView.h"
#include "core/layout/api/LayoutAPIShim.h"
#include "core/layout/api/LayoutViewItem.h"
#include "core/layout/api/LineLayoutAPIShim.h"
#include "core/loader/ProgressTracker.h"
#include "core/page/Page.h"
#include "core/paint/PaintLayer.h"
#include "core/style/ComputedStyleConstants.h"
#include "core/svg/SVGDocumentExtensions.h"
#include "core/svg/SVGSVGElement.h"
#include "core/svg/graphics/SVGImage.h"
#include "modules/accessibility/AXImageMapLink.h"
#include "modules/accessibility/AXInlineTextBox.h"
#include "modules/accessibility/AXObjectCacheImpl.h"
#include "modules/accessibility/AXSVGRoot.h"
#include "modules/accessibility/AXSpinButton.h"
#include "modules/accessibility/AXTable.h"
#include "platform/fonts/FontTraits.h"
#include "platform/geometry/TransformState.h"
#include "platform/text/PlatformLocale.h"
#include "platform/text/TextDirection.h"
#include "wtf/StdLibExtras.h"
using blink::WebLocalizedString;
namespace blink {
using namespace HTMLNames;
static inline LayoutObject* firstChildInContinuation(
const LayoutInline& layoutObject) {
LayoutBoxModelObject* r = layoutObject.continuation();
while (r) {
if (r->isLayoutBlock())
return r;
if (LayoutObject* child = r->slowFirstChild())
return child;
r = toLayoutInline(r)->continuation();
}
return 0;
}
static inline bool isInlineWithContinuation(LayoutObject* object) {
if (!object->isBoxModelObject())
return false;
LayoutBoxModelObject* layoutObject = toLayoutBoxModelObject(object);
if (!layoutObject->isLayoutInline())
return false;
return toLayoutInline(layoutObject)->continuation();
}
static inline LayoutObject* firstChildConsideringContinuation(
LayoutObject* layoutObject) {
LayoutObject* firstChild = layoutObject->slowFirstChild();
// CSS first-letter pseudo element is handled as continuation. Returning it
// will result in duplicated elements.
if (firstChild && firstChild->isText() &&
toLayoutText(firstChild)->isTextFragment() &&
toLayoutTextFragment(firstChild)->firstLetterPseudoElement())
return nullptr;
if (!firstChild && isInlineWithContinuation(layoutObject))
firstChild = firstChildInContinuation(toLayoutInline(*layoutObject));
return firstChild;
}
static inline LayoutInline* startOfContinuations(LayoutObject* r) {
if (r->isInlineElementContinuation()) {
return toLayoutInline(r->node()->layoutObject());
}
// Blocks with a previous continuation always have a next continuation
if (r->isLayoutBlockFlow() &&
toLayoutBlockFlow(r)->inlineElementContinuation())
return toLayoutInline(toLayoutBlockFlow(r)
->inlineElementContinuation()
->node()
->layoutObject());
return 0;
}
static inline LayoutObject* endOfContinuations(LayoutObject* layoutObject) {
LayoutObject* prev = layoutObject;
LayoutObject* cur = layoutObject;
if (!cur->isLayoutInline() && !cur->isLayoutBlockFlow())
return layoutObject;
while (cur) {
prev = cur;
if (cur->isLayoutInline()) {
cur = toLayoutInline(cur)->inlineElementContinuation();
ASSERT(cur || !toLayoutInline(prev)->continuation());
} else {
cur = toLayoutBlockFlow(cur)->inlineElementContinuation();
}
}
return prev;
}
static inline bool lastChildHasContinuation(LayoutObject* layoutObject) {
LayoutObject* lastChild = layoutObject->slowLastChild();
return lastChild && isInlineWithContinuation(lastChild);
}
static LayoutBoxModelObject* nextContinuation(LayoutObject* layoutObject) {
ASSERT(layoutObject);
if (layoutObject->isLayoutInline() && !layoutObject->isAtomicInlineLevel())
return toLayoutInline(layoutObject)->continuation();
if (layoutObject->isLayoutBlockFlow())
return toLayoutBlockFlow(layoutObject)->inlineElementContinuation();
return 0;
}
AXLayoutObject::AXLayoutObject(LayoutObject* layoutObject,
AXObjectCacheImpl& axObjectCache)
: AXNodeObject(layoutObject->node(), axObjectCache),
m_layoutObject(layoutObject) {
#if DCHECK_IS_ON()
m_layoutObject->setHasAXObject(true);
#endif
}
AXLayoutObject* AXLayoutObject::create(LayoutObject* layoutObject,
AXObjectCacheImpl& axObjectCache) {
return new AXLayoutObject(layoutObject, axObjectCache);
}
AXLayoutObject::~AXLayoutObject() {
ASSERT(isDetached());
}
LayoutBoxModelObject* AXLayoutObject::getLayoutBoxModelObject() const {
if (!m_layoutObject || !m_layoutObject->isBoxModelObject())
return 0;
return toLayoutBoxModelObject(m_layoutObject);
}
ScrollableArea* AXLayoutObject::getScrollableAreaIfScrollable() const {
if (isWebArea())
return documentFrameView()->layoutViewportScrollableArea();
if (!m_layoutObject || !m_layoutObject->isBox())
return 0;
LayoutBox* box = toLayoutBox(m_layoutObject);
if (!box->canBeScrolledAndHasScrollableArea())
return 0;
return box->getScrollableArea();
}
static bool isImageOrAltText(LayoutBoxModelObject* box, Node* node) {
if (box && box->isImage())
return true;
if (isHTMLImageElement(node))
return true;
if (isHTMLInputElement(node) &&
toHTMLInputElement(node)->hasFallbackContent())
return true;
return false;
}
AccessibilityRole AXLayoutObject::nativeAccessibilityRoleIgnoringAria() const {
Node* node = m_layoutObject->node();
LayoutBoxModelObject* cssBox = getLayoutBoxModelObject();
if ((cssBox && cssBox->isListItem()) || isHTMLLIElement(node))
return ListItemRole;
if (m_layoutObject->isListMarker())
return ListMarkerRole;
if (m_layoutObject->isBR())
return LineBreakRole;
if (m_layoutObject->isText())
return StaticTextRole;
if (cssBox && isImageOrAltText(cssBox, node)) {
if (node && node->isLink())
return ImageMapRole;
if (isHTMLInputElement(node))
return ariaHasPopup() ? PopUpButtonRole : ButtonRole;
if (isSVGImage())
return SVGRootRole;
return ImageRole;
}
// Note: if JavaScript is disabled, the layoutObject won't be a
// LayoutHTMLCanvas.
if (isHTMLCanvasElement(node) && m_layoutObject->isCanvas())
return CanvasRole;
if (cssBox && cssBox->isLayoutView())
return WebAreaRole;
if (m_layoutObject->isSVGImage())
return ImageRole;
if (m_layoutObject->isSVGRoot())
return SVGRootRole;
// Table sections should be ignored.
if (m_layoutObject->isTableSection())
return IgnoredRole;
if (m_layoutObject->isHR())
return SplitterRole;
return AXNodeObject::nativeAccessibilityRoleIgnoringAria();
}
AccessibilityRole AXLayoutObject::determineAccessibilityRole() {
if (!m_layoutObject)
return UnknownRole;
if ((m_ariaRole = determineAriaRoleAttribute()) != UnknownRole)
return m_ariaRole;
AccessibilityRole role = nativeAccessibilityRoleIgnoringAria();
if (role != UnknownRole)
return role;
if (m_layoutObject->isLayoutBlockFlow())
return GroupRole;
// If the element does not have role, but it has ARIA attributes,
// accessibility should fallback to exposing it as a group.
if (supportsARIAAttributes())
return GroupRole;
return UnknownRole;
}
void AXLayoutObject::init() {
AXNodeObject::init();
}
void AXLayoutObject::detach() {
AXNodeObject::detach();
detachRemoteSVGRoot();
#if DCHECK_IS_ON()
if (m_layoutObject)
m_layoutObject->setHasAXObject(false);
#endif
m_layoutObject = 0;
}
//
// Check object role or purpose.
//
static bool isLinkable(const AXObject& object) {
if (!object.getLayoutObject())
return false;
// See https://wiki.mozilla.org/Accessibility/AT-Windows-API for the elements
// Mozilla considers linkable.
return object.isLink() || object.isImage() ||
object.getLayoutObject()->isText();
}
// Requires layoutObject to be present because it relies on style
// user-modify. Don't move this logic to AXNodeObject.
bool AXLayoutObject::isEditable() const {
if (getLayoutObject() && getLayoutObject()->isTextControl())
return true;
if (getNode() && hasEditableStyle(*getNode()))
return true;
if (isWebArea()) {
Document& document = getLayoutObject()->document();
HTMLElement* body = document.body();
if (body && hasEditableStyle(*body)) {
AXObject* axBody = axObjectCache().getOrCreate(body);
return axBody && axBody != axBody->ariaHiddenRoot();
}
return hasEditableStyle(document);
}
return AXNodeObject::isEditable();
}
// Requires layoutObject to be present because it relies on style
// user-modify. Don't move this logic to AXNodeObject.
bool AXLayoutObject::isRichlyEditable() const {
if (getNode() && hasRichlyEditableStyle(*getNode()))
return true;
if (isWebArea()) {
Document& document = m_layoutObject->document();
HTMLElement* body = document.body();
if (body && hasRichlyEditableStyle(*body)) {
AXObject* axBody = axObjectCache().getOrCreate(body);
return axBody && axBody != axBody->ariaHiddenRoot();
}
return hasRichlyEditableStyle(document);
}
return AXNodeObject::isRichlyEditable();
}
bool AXLayoutObject::isLinked() const {
if (!isLinkable(*this))
return false;
Element* anchor = anchorElement();
if (!isHTMLAnchorElement(anchor))
return false;
return !toHTMLAnchorElement(*anchor).href().isEmpty();
}
bool AXLayoutObject::isLoaded() const {
return !m_layoutObject->document().parser();
}
bool AXLayoutObject::isOffScreen() const {
ASSERT(m_layoutObject);
IntRect contentRect =
pixelSnappedIntRect(m_layoutObject->absoluteVisualRect());
FrameView* view = m_layoutObject->frame()->view();
IntRect viewRect = view->visibleContentRect();
viewRect.intersect(contentRect);
return viewRect.isEmpty();
}
bool AXLayoutObject::isReadOnly() const {
ASSERT(m_layoutObject);
if (isWebArea()) {
Document& document = m_layoutObject->document();
HTMLElement* body = document.body();
if (body && hasEditableStyle(*body)) {
AXObject* axBody = axObjectCache().getOrCreate(body);
return !axBody || axBody == axBody->ariaHiddenRoot();
}
return !hasEditableStyle(document);
}
return AXNodeObject::isReadOnly();
}
bool AXLayoutObject::isVisited() const {
// FIXME: Is it a privacy violation to expose visited information to
// accessibility APIs?
return m_layoutObject->style()->isLink() &&
m_layoutObject->style()->insideLink() ==
EInsideLink::kInsideVisitedLink;
}
//
// Check object state.
//
bool AXLayoutObject::isFocused() const {
if (!getDocument())
return false;
Element* focusedElement = getDocument()->focusedElement();
if (!focusedElement)
return false;
AXObject* focusedObject = axObjectCache().getOrCreate(focusedElement);
if (!focusedObject || !focusedObject->isAXLayoutObject())
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 (focusedObject == this ||
(roleValue() == WebAreaRole &&
getDocument()->frame()->selection().isFocusedAndActive()))
return true;
return false;
}
bool AXLayoutObject::isSelected() const {
if (!getLayoutObject() || !getNode())
return false;
const AtomicString& ariaSelected = getAttribute(aria_selectedAttr);
if (equalIgnoringCase(ariaSelected, "true"))
return true;
AXObject* focusedObject = axObjectCache().focusedObject();
if (ariaRoleAttribute() == ListBoxOptionRole && focusedObject &&
focusedObject->activeDescendant() == this) {
return true;
}
if (isTabItem() && isTabItemSelected())
return true;
return false;
}
//
// Whether objects are ignored, i.e. not included in the tree.
//
AXObjectInclusion AXLayoutObject::defaultObjectInclusion(
IgnoredReasons* ignoredReasons) const {
// The following cases can apply to any element that's a subclass of
// AXLayoutObject.
if (!m_layoutObject) {
if (ignoredReasons)
ignoredReasons->push_back(IgnoredReason(AXNotRendered));
return IgnoreObject;
}
if (m_layoutObject->style()->visibility() != EVisibility::kVisible) {
// aria-hidden is meant to override visibility as the determinant in AX
// hierarchy inclusion.
if (equalIgnoringCase(getAttribute(aria_hiddenAttr), "false"))
return DefaultBehavior;
if (ignoredReasons)
ignoredReasons->push_back(IgnoredReason(AXNotVisible));
return IgnoreObject;
}
return AXObject::defaultObjectInclusion(ignoredReasons);
}
bool AXLayoutObject::computeAccessibilityIsIgnored(
IgnoredReasons* ignoredReasons) const {
#if DCHECK_IS_ON()
ASSERT(m_initialized);
#endif
if (!m_layoutObject)
return true;
// Check first if any of the common reasons cause this element to be ignored.
// Then process other use cases that need to be applied to all the various
// roles that AXLayoutObjects take on.
AXObjectInclusion decision = defaultObjectInclusion(ignoredReasons);
if (decision == IncludeObject)
return false;
if (decision == IgnoreObject)
return true;
if (m_layoutObject->isAnonymousBlock())
return true;
// If this element is within a parent that cannot have children, it should not
// be exposed.
if (isDescendantOfLeafNode()) {
if (ignoredReasons)
ignoredReasons->push_back(
IgnoredReason(AXAncestorIsLeafNode, leafNodeAncestor()));
return true;
}
if (roleValue() == IgnoredRole) {
if (ignoredReasons)
ignoredReasons->push_back(IgnoredReason(AXUninteresting));
return true;
}
if (hasInheritedPresentationalRole()) {
if (ignoredReasons) {
const AXObject* inheritsFrom = inheritsPresentationalRoleFrom();
if (inheritsFrom == this)
ignoredReasons->push_back(IgnoredReason(AXPresentationalRole));
else
ignoredReasons->push_back(
IgnoredReason(AXInheritsPresentation, inheritsFrom));
}
return true;
}
// An ARIA tree can only have tree items and static text as children.
if (AXObject* treeAncestor = treeAncestorDisallowingChild()) {
if (ignoredReasons)
ignoredReasons->push_back(
IgnoredReason(AXAncestorDisallowsChild, treeAncestor));
return true;
}
// A LayoutPart is an iframe element or embedded object element or something
// like that. We don't want to ignore those.
if (m_layoutObject->isLayoutPart())
return false;
// Make sure renderers with layers stay in the tree.
if (getLayoutObject() && getLayoutObject()->hasLayer() && getNode() &&
getNode()->hasChildren())
return false;
// 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.
AXObject* controlObject = correspondingControlForLabelElement();
if (controlObject && controlObject->isCheckboxOrRadio() &&
controlObject->nameFromLabelElement()) {
if (ignoredReasons) {
HTMLLabelElement* label = labelElementContainer();
if (label && label != getNode()) {
AXObject* labelAXObject = axObjectCache().getOrCreate(label);
ignoredReasons->push_back(
IgnoredReason(AXLabelContainer, labelAXObject));
}
ignoredReasons->push_back(IgnoredReason(AXLabelFor, controlObject));
}
return true;
}
if (m_layoutObject->isBR())
return false;
if (isLink())
return false;
if (isInPageLinkTarget())
return false;
if (m_layoutObject->isText()) {
// Static text beneath MenuItems and MenuButtons are just reported along
// with the menu item, so it's ignored on an individual level.
AXObject* parent = parentObjectUnignored();
if (parent && (parent->ariaRoleAttribute() == MenuItemRole ||
parent->ariaRoleAttribute() == MenuButtonRole)) {
if (ignoredReasons)
ignoredReasons->push_back(
IgnoredReason(AXStaticTextUsedAsNameFor, parent));
return true;
}
LayoutText* layoutText = toLayoutText(m_layoutObject);
if (!layoutText->hasTextBoxes()) {
if (ignoredReasons)
ignoredReasons->push_back(IgnoredReason(AXEmptyText));
return true;
}
// Don't ignore static text in editable text controls.
for (AXObject* parent = parentObject(); parent;
parent = parent->parentObject()) {
if (parent->roleValue() == TextFieldRole)
return false;
}
// Text elements that are just empty whitespace should not be returned.
// FIXME(dmazzoni): we probably shouldn't ignore this if the style is 'pre',
// or similar...
if (layoutText->text().impl()->containsOnlyWhitespace()) {
if (ignoredReasons)
ignoredReasons->push_back(IgnoredReason(AXEmptyText));
return true;
}
return false;
}
if (isHeading())
return false;
if (isLandmarkRelated())
return false;
// Header and footer tags may also be exposed as landmark roles but not
// always.
if (getNode() &&
(getNode()->hasTagName(headerTag) || getNode()->hasTagName(footerTag)))
return false;
// all controls are accessible
if (isControl())
return false;
if (ariaRoleAttribute() != UnknownRole)
return false;
// don't ignore labels, because they serve as TitleUIElements
Node* node = m_layoutObject->node();
if (isHTMLLabelElement(node))
return false;
// Anything that is content editable should not be ignored.
// However, one cannot just call node->hasEditableStyle() since that will ask
// if its parents are also editable. Only the top level content editable
// region should be exposed.
if (hasContentEditableAttributeSet())
return false;
if (roleValue() == AbbrRole)
return false;
// List items play an important role in defining the structure of lists. They
// should not be ignored.
if (roleValue() == ListItemRole)
return false;
if (roleValue() == BlockquoteRole)
return false;
if (roleValue() == DialogRole)
return false;
if (roleValue() == FigcaptionRole)
return false;
if (roleValue() == FigureRole)
return false;
if (roleValue() == DetailsRole)
return false;
if (roleValue() == MarkRole)
return false;
if (roleValue() == MathRole)
return false;
if (roleValue() == MeterRole)
return false;
if (roleValue() == RubyRole)
return false;
if (roleValue() == SplitterRole)
return false;
if (roleValue() == TimeRole)
return false;
// if this element has aria attributes on it, it should not be ignored.
if (supportsARIAAttributes())
return false;
// <span> tags are inline tags and not meant to convey information if they
// have no other aria information on them. If we don't ignore them, they may
// emit signals expected to come from their parent. In addition, because
// included spans are GroupRole objects, and GroupRole objects are often
// containers with meaningful information, the inclusion of a span can have
// the side effect of causing the immediate parent accessible to be ignored.
// This is especially problematic for platforms which have distinct roles for
// textual block elements.
if (isHTMLSpanElement(node)) {
if (ignoredReasons)
ignoredReasons->push_back(IgnoredReason(AXUninteresting));
return true;
}
if (isImage())
return false;
if (isCanvas()) {
if (canvasHasFallbackContent())
return false;
LayoutHTMLCanvas* canvas = toLayoutHTMLCanvas(m_layoutObject);
if (canvas->size().height() <= 1 || canvas->size().width() <= 1) {
if (ignoredReasons)
ignoredReasons->push_back(IgnoredReason(AXProbablyPresentational));
return true;
}
// Otherwise fall through; use presence of help text, title, or description
// to decide.
}
if (isWebArea() || m_layoutObject->isListMarker())
return false;
// Using the help text, title or accessibility description (so we
// check if there's some kind of accessible name for the element)
// to decide an element's visibility is not as definitive as
// previous checks, so this should remain as one of the last.
//
// These checks are simplified in the interest of execution speed;
// for example, any element having an alt attribute will make it
// not ignored, rather than just images.
if (!getAttribute(aria_helpAttr).isEmpty() ||
!getAttribute(aria_describedbyAttr).isEmpty() ||
!getAttribute(altAttr).isEmpty() || !getAttribute(titleAttr).isEmpty())
return false;
// Don't ignore generic focusable elements like <div tabindex=0>
// unless they're completely empty, with no children.
if (isGenericFocusableElement() && node->hasChildren())
return false;
if (isScrollableContainer())
return false;
// Ignore layout objects that are block flows with inline children. These
// are usually dummy layout objects that pad out the tree, but there are
// some exceptions below.
if (m_layoutObject->isLayoutBlockFlow() && m_layoutObject->childrenInline() &&
!canSetFocusAttribute()) {
// If the layout object has any plain text in it, that text will be
// inside a LineBox, so the layout object will have a first LineBox.
bool hasAnyText = !!toLayoutBlockFlow(m_layoutObject)->firstLineBox();
// Always include interesting-looking objects.
if (hasAnyText || mouseButtonListener())
return false;
if (ignoredReasons)
ignoredReasons->push_back(IgnoredReason(AXUninteresting));
return true;
}
// By default, objects should be ignored so that the AX hierarchy is not
// filled with unnecessary items.
if (ignoredReasons)
ignoredReasons->push_back(IgnoredReason(AXUninteresting));
return true;
}
//
// Properties of static elements.
//
const AtomicString& AXLayoutObject::accessKey() const {
Node* node = m_layoutObject->node();
if (!node)
return nullAtom;
if (!node->isElementNode())
return nullAtom;
return toElement(node)->getAttribute(accesskeyAttr);
}
RGBA32 AXLayoutObject::computeBackgroundColor() const {
if (!getLayoutObject())
return AXNodeObject::backgroundColor();
Color blendedColor = Color::transparent;
// Color::blend should be called like this: background.blend(foreground).
for (LayoutObject* layoutObject = getLayoutObject(); layoutObject;
layoutObject = layoutObject->parent()) {
const AXObject* axParent = axObjectCache().getOrCreate(layoutObject);
if (axParent && axParent != this) {
Color parentColor = axParent->backgroundColor();
blendedColor = parentColor.blend(blendedColor);
return blendedColor.rgb();
}
const ComputedStyle* style = layoutObject->style();
if (!style || !style->hasBackground())
continue;
Color currentColor =
style->visitedDependentColor(CSSPropertyBackgroundColor);
blendedColor = currentColor.blend(blendedColor);
// Continue blending until we get no transparency.
if (!blendedColor.hasAlpha())
break;
}
// If we still have some transparency, blend in the document base color.
if (blendedColor.hasAlpha()) {
FrameView* view = documentFrameView();
if (view) {
Color documentBaseColor = view->baseBackgroundColor();
blendedColor = documentBaseColor.blend(blendedColor);
} else {
// Default to a white background.
blendedColor.blendWithWhite();
}
}
return blendedColor.rgb();
}
RGBA32 AXLayoutObject::color() const {
if (!getLayoutObject() || isColorWell())
return AXNodeObject::color();
const ComputedStyle* style = getLayoutObject()->style();
if (!style)
return AXNodeObject::color();
Color color = style->visitedDependentColor(CSSPropertyColor);
return color.rgb();
}
String AXLayoutObject::fontFamily() const {
if (!getLayoutObject())
return AXNodeObject::fontFamily();
const ComputedStyle* style = getLayoutObject()->style();
if (!style)
return AXNodeObject::fontFamily();
FontDescription& fontDescription =
const_cast<FontDescription&>(style->getFontDescription());
return fontDescription.firstFamily().family();
}
// Font size is in pixels.
float AXLayoutObject::fontSize() const {
if (!getLayoutObject())
return AXNodeObject::fontSize();
const ComputedStyle* style = getLayoutObject()->style();
if (!style)
return AXNodeObject::fontSize();
return style->computedFontSize();
}
String AXLayoutObject::imageDataUrl(const IntSize& maxSize) const {
Node* node = getNode();
if (!node)
return String();
ImageBitmapOptions options;
ImageBitmap* imageBitmap = nullptr;
Document* document = &node->document();
if (isHTMLImageElement(node)) {
imageBitmap = ImageBitmap::create(toHTMLImageElement(node),
Optional<IntRect>(), document, options);
} else if (isHTMLCanvasElement(node)) {
imageBitmap = ImageBitmap::create(toHTMLCanvasElement(node),
Optional<IntRect>(), options);
} else if (isHTMLVideoElement(node)) {
imageBitmap = ImageBitmap::create(toHTMLVideoElement(node),
Optional<IntRect>(), document, options);
}
if (!imageBitmap)
return String();
StaticBitmapImage* bitmapImage = imageBitmap->bitmapImage();
if (!bitmapImage)
return String();
sk_sp<SkImage> image = bitmapImage->imageForCurrentFrame();
if (!image || image->width() <= 0 || image->height() <= 0)
return String();
// Determine the width and height of the output image, using a proportional
// scale factor such that it's no larger than |maxSize|, if |maxSize| is not
// empty. It only resizes the image to be smaller (if necessary), not
// larger.
float xScale = maxSize.width() ? maxSize.width() * 1.0 / image->width() : 1.0;
float yScale =
maxSize.height() ? maxSize.height() * 1.0 / image->height() : 1.0;
float scale = std::min(xScale, yScale);
if (scale >= 1.0)
scale = 1.0;
int width = std::round(image->width() * scale);
int height = std::round(image->height() * scale);
// Draw the scaled image into a bitmap in native format.
SkBitmap bitmap;
bitmap.allocPixels(SkImageInfo::MakeN32(width, height, kPremul_SkAlphaType));
SkCanvas canvas(bitmap);
canvas.clear(SK_ColorTRANSPARENT);
canvas.drawImageRect(image, SkRect::MakeIWH(width, height), nullptr);
// Copy the bits into a buffer in RGBA_8888 unpremultiplied format
// for encoding.
SkImageInfo info = SkImageInfo::Make(width, height, kRGBA_8888_SkColorType,
kUnpremul_SkAlphaType);
size_t rowBytes = info.minRowBytes();
Vector<char> pixelStorage(info.getSafeSize(rowBytes));
SkPixmap pixmap(info, pixelStorage.data(), rowBytes);
if (!SkImage::MakeFromBitmap(bitmap)->readPixels(pixmap, 0, 0))
return String();
// Encode as a PNG and return as a data url.
String dataUrl =
ImageDataBuffer(
IntSize(width, height),
reinterpret_cast<const unsigned char*>(pixelStorage.data()))
.toDataURL("image/png", 1.0);
return dataUrl;
}
String AXLayoutObject::text() const {
if (isPasswordFieldAndShouldHideValue()) {
if (!getLayoutObject())
return String();
const ComputedStyle* style = getLayoutObject()->style();
if (!style)
return String();
unsigned unmaskedTextLength = AXNodeObject::text().length();
if (!unmaskedTextLength)
return String();
UChar maskCharacter = 0;
switch (style->textSecurity()) {
case TSNONE:
break; // Fall through to the non-password branch.
case TSDISC:
maskCharacter = bulletCharacter;
break;
case TSCIRCLE:
maskCharacter = whiteBulletCharacter;
break;
case TSSQUARE:
maskCharacter = blackSquareCharacter;
break;
}
if (maskCharacter) {
StringBuilder maskedText;
maskedText.reserveCapacity(unmaskedTextLength);
for (unsigned i = 0; i < unmaskedTextLength; ++i)
maskedText.append(maskCharacter);
return maskedText.toString();
}
}
return AXNodeObject::text();
}
AccessibilityTextDirection AXLayoutObject::textDirection() const {
if (!getLayoutObject())
return AXNodeObject::textDirection();
const ComputedStyle* style = getLayoutObject()->style();
if (!style)
return AXNodeObject::textDirection();
if (style->isHorizontalWritingMode()) {
switch (style->direction()) {
case TextDirection::kLtr:
return AccessibilityTextDirectionLTR;
case TextDirection::kRtl:
return AccessibilityTextDirectionRTL;
}
} else {
switch (style->direction()) {
case TextDirection::kLtr:
return AccessibilityTextDirectionTTB;
case TextDirection::kRtl:
return AccessibilityTextDirectionBTT;
}
}
return AXNodeObject::textDirection();
}
int AXLayoutObject::textLength() const {
if (!isTextControl())
return -1;
return text().length();
}
TextStyle AXLayoutObject::getTextStyle() const {
if (!getLayoutObject())
return AXNodeObject::getTextStyle();
const ComputedStyle* style = getLayoutObject()->style();
if (!style)
return AXNodeObject::getTextStyle();
unsigned textStyle = TextStyleNone;
if (style->fontWeight() == FontWeightBold)
textStyle |= TextStyleBold;
if (style->getFontDescription().style() == FontStyleItalic)
textStyle |= TextStyleItalic;
if (style->getTextDecoration() == TextDecorationUnderline)
textStyle |= TextStyleUnderline;
if (style->getTextDecoration() == TextDecorationLineThrough)
textStyle |= TextStyleLineThrough;
return static_cast<TextStyle>(textStyle);
}
KURL AXLayoutObject::url() const {
if (isAnchor() && isHTMLAnchorElement(m_layoutObject->node())) {
if (HTMLAnchorElement* anchor = toHTMLAnchorElement(anchorElement()))
return anchor->href();
}
if (isWebArea())
return m_layoutObject->document().url();
if (isImage() && isHTMLImageElement(m_layoutObject->node()))
return toHTMLImageElement(*m_layoutObject->node()).src();
if (isInputImage())
return toHTMLInputElement(m_layoutObject->node())->src();
return KURL();
}
//
// Inline text boxes.
//
void AXLayoutObject::loadInlineTextBoxes() {
if (!getLayoutObject() || !getLayoutObject()->isText())
return;
clearChildren();
addInlineTextBoxChildren(true);
}
AXObject* AXLayoutObject::nextOnLine() const {
if (!getLayoutObject())
return nullptr;
InlineBox* inlineBox = nullptr;
if (getLayoutObject()->isLayoutInline())
inlineBox = toLayoutInline(getLayoutObject())->lastLineBox();
else if (getLayoutObject()->isText())
inlineBox = toLayoutText(getLayoutObject())->lastTextBox();
if (!inlineBox)
return nullptr;
AXObject* result = nullptr;
for (InlineBox* next = inlineBox->nextOnLine(); next;
next = next->nextOnLine()) {
LayoutObject* layoutObject =
LineLayoutAPIShim::layoutObjectFrom(next->getLineLayoutItem());
result = axObjectCache().getOrCreate(layoutObject);
if (result)
break;
}
// A static text node might span multiple lines. Try to return the first
// inline text box within that static text if possible.
if (result && result->roleValue() == StaticTextRole &&
result->children().size())
result = result->children()[0].get();
return result;
}
AXObject* AXLayoutObject::previousOnLine() const {
if (!getLayoutObject())
return nullptr;
InlineBox* inlineBox = nullptr;
if (getLayoutObject()->isLayoutInline())
inlineBox = toLayoutInline(getLayoutObject())->firstLineBox();
else if (getLayoutObject()->isText())
inlineBox = toLayoutText(getLayoutObject())->firstTextBox();
if (!inlineBox)
return nullptr;
AXObject* result = nullptr;
for (InlineBox* prev = inlineBox->prevOnLine(); prev;
prev = prev->prevOnLine()) {
LayoutObject* layoutObject =
LineLayoutAPIShim::layoutObjectFrom(prev->getLineLayoutItem());
result = axObjectCache().getOrCreate(layoutObject);
if (result)
break;
}
// A static text node might span multiple lines. Try to return the last inline
// text box within that static text if possible.
if (result && result->roleValue() == StaticTextRole &&
result->children().size())
result = result->children()[result->children().size() - 1].get();
return result;
}
//
// Properties of interactive elements.
//
String AXLayoutObject::stringValue() const {
if (!m_layoutObject)
return String();
LayoutBoxModelObject* cssBox = getLayoutBoxModelObject();
if (cssBox && cssBox->isMenuList()) {
// LayoutMenuList will go straight to the text() of its selected item.
// This has to be overridden in the case where the selected item has an ARIA
// label.
HTMLSelectElement* selectElement =
toHTMLSelectElement(m_layoutObject->node());
int selectedIndex = selectElement->selectedIndex();
const HeapVector<Member<HTMLElement>>& listItems =
selectElement->listItems();
if (selectedIndex >= 0 &&
static_cast<size_t>(selectedIndex) < listItems.size()) {
const AtomicString& overriddenDescription =
listItems[selectedIndex]->fastGetAttribute(aria_labelAttr);
if (!overriddenDescription.isNull())
return overriddenDescription;
}
return toLayoutMenuList(m_layoutObject)->text();
}
if (isWebArea()) {
// FIXME: Why would a layoutObject exist when the Document isn't attached to
// a frame?
if (m_layoutObject->frame())
return String();
ASSERT_NOT_REACHED();
}
if (isTextControl())
return text();
if (m_layoutObject->isFileUploadControl())
return toLayoutFileUploadControl(m_layoutObject)->fileTextValue();
// Handle other HTML input elements that aren't text controls, like date and
// time controls, by returning the string value, with the exception of
// checkboxes and radio buttons (which would return "on").
if (getNode() && isHTMLInputElement(getNode())) {
HTMLInputElement* input = toHTMLInputElement(getNode());
if (input->type() != InputTypeNames::checkbox &&
input->type() != InputTypeNames::radio)
return input->value();
}
// 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();
}
String AXLayoutObject::textAlternative(bool recursive,
bool inAriaLabelledByTraversal,
AXObjectSet& visited,
AXNameFrom& nameFrom,
AXRelatedObjectVector* relatedObjects,
NameSources* nameSources) const {
if (m_layoutObject) {
String textAlternative;
bool foundTextAlternative = false;
if (m_layoutObject->isBR()) {
textAlternative = String("\n");
foundTextAlternative = true;
} else if (m_layoutObject->isText() &&
(!recursive || !m_layoutObject->isCounter())) {
LayoutText* layoutText = toLayoutText(m_layoutObject);
String result = layoutText->plainText();
if (!result.isEmpty() || layoutText->isAllCollapsibleWhitespace())
textAlternative = result;
else
textAlternative = layoutText->text();
foundTextAlternative = true;
} else if (m_layoutObject->isListMarker() && !recursive) {
textAlternative = toLayoutListMarker(m_layoutObject)->text();
foundTextAlternative = true;
}
if (foundTextAlternative) {
nameFrom = AXNameFromContents;
if (nameSources) {
nameSources->push_back(NameSource(false));
nameSources->back().type = nameFrom;
nameSources->back().text = textAlternative;
}
return textAlternative;
}
}
return AXNodeObject::textAlternative(recursive, inAriaLabelledByTraversal,
visited, nameFrom, relatedObjects,
nameSources);
}
//
// ARIA attributes.
//
void AXLayoutObject::ariaOwnsElements(AXObjectVector& owns) const {
accessibilityChildrenFromAttribute(aria_ownsAttr, owns);
}
void AXLayoutObject::ariaDescribedbyElements(
AXObjectVector& describedby) const {
accessibilityChildrenFromAttribute(aria_describedbyAttr, describedby);
}
void AXLayoutObject::ariaLabelledbyElements(AXObjectVector& labelledby) const {
accessibilityChildrenFromAttribute(aria_labelledbyAttr, labelledby);
}
bool AXLayoutObject::ariaHasPopup() const {
return elementAttributeValue(aria_haspopupAttr);
}
bool AXLayoutObject::ariaRoleHasPresentationalChildren() const {
switch (m_ariaRole) {
case ButtonRole:
case SliderRole:
case ImageRole:
case ProgressIndicatorRole:
case SpinButtonRole:
// case SeparatorRole:
return true;
default:
return false;
}
}
AXObject* AXLayoutObject::ancestorForWhichThisIsAPresentationalChild() const {
// Walk the parent chain looking for a parent that has presentational children
AXObject* parent = parentObjectIfExists();
while (parent) {
if (parent->ariaRoleHasPresentationalChildren())
break;
// The descendants of a AXMenuList that are AXLayoutObjects are all
// presentational. (The real descendants are an AXMenuListPopup and
// AXMenuListOptions, which are not AXLayoutObjects.)
if (parent->isMenuList())
break;
parent = parent->parentObjectIfExists();
}
return parent;
}
bool AXLayoutObject::supportsARIADragging() const {
const AtomicString& grabbed = getAttribute(aria_grabbedAttr);
return equalIgnoringCase(grabbed, "true") ||
equalIgnoringCase(grabbed, "false");
}
bool AXLayoutObject::supportsARIADropping() const {
const AtomicString& dropEffect = getAttribute(aria_dropeffectAttr);
return !dropEffect.isEmpty();
}
bool AXLayoutObject::supportsARIAFlowTo() const {
return !getAttribute(aria_flowtoAttr).isEmpty();
}
bool AXLayoutObject::supportsARIAOwns() const {
if (!m_layoutObject)
return false;
const AtomicString& ariaOwns = getAttribute(aria_ownsAttr);
return !ariaOwns.isEmpty();
}
//
// ARIA live-region features.
//
const AtomicString& AXLayoutObject::liveRegionStatus() const {
DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusAssertive,
("assertive"));
DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusPolite, ("polite"));
DEFINE_STATIC_LOCAL(const AtomicString, liveRegionStatusOff, ("off"));
const AtomicString& liveRegionStatus = getAttribute(aria_liveAttr);
// These roles have implicit live region status.
if (liveRegionStatus.isEmpty()) {
switch (roleValue()) {
case AlertDialogRole:
case AlertRole:
return liveRegionStatusAssertive;
case LogRole:
case StatusRole:
return liveRegionStatusPolite;
case TimerRole:
case MarqueeRole:
return liveRegionStatusOff;
default:
break;
}
}
return liveRegionStatus;
}
const AtomicString& AXLayoutObject::liveRegionRelevant() const {
DEFINE_STATIC_LOCAL(const AtomicString, defaultLiveRegionRelevant,
("additions text"));
const AtomicString& relevant = getAttribute(aria_relevantAttr);
// Default aria-relevant = "additions text".
if (relevant.isEmpty())
return defaultLiveRegionRelevant;
return relevant;
}
bool AXLayoutObject::liveRegionAtomic() const {
// ARIA roles "alert" and "status" should have an implicit aria-atomic value
// of true.
if (getAttribute(aria_atomicAttr).isEmpty() &&
(roleValue() == AlertRole || roleValue() == StatusRole)) {
return true;
}
return elementAttributeValue(aria_atomicAttr);
}
bool AXLayoutObject::liveRegionBusy() const {
return elementAttributeValue(aria_busyAttr);
}
//
// Hit testing.
//
AXObject* AXLayoutObject::accessibilityHitTest(const IntPoint& point) const {
if (!m_layoutObject || !m_layoutObject->hasLayer())
return nullptr;
PaintLayer* layer = toLayoutBox(m_layoutObject)->layer();
HitTestRequest request(HitTestRequest::ReadOnly | HitTestRequest::Active);
HitTestResult hitTestResult = HitTestResult(request, point);
layer->hitTest(hitTestResult);
Node* node = hitTestResult.innerNode();
if (!node)
return nullptr;
if (isHTMLAreaElement(node))
return accessibilityImageMapHitTest(toHTMLAreaElement(node), point);
if (isHTMLOptionElement(node)) {
node = toHTMLOptionElement(*node).ownerSelectElement();
if (!node)
return nullptr;
}
LayoutObject* obj = node->layoutObject();
if (!obj)
return nullptr;
AXObject* result = axObjectCache().getOrCreate(obj);
result->updateChildrenIfNecessary();
// Allow the element to perform any hit-testing it might need to do to reach
// non-layout children.
result = result->elementAccessibilityHitTest(point);
if (result && result->accessibilityIsIgnored()) {
// If this element is the label of a control, a hit test should return the
// control.
if (result->isAXLayoutObject()) {
AXObject* controlObject =
toAXLayoutObject(result)->correspondingControlForLabelElement();
if (controlObject && controlObject->nameFromLabelElement())
return controlObject;
}
result = result->parentObjectUnignored();
}
return result;
}
AXObject* AXLayoutObject::elementAccessibilityHitTest(
const IntPoint& point) const {
if (isSVGImage())
return remoteSVGElementHitTest(point);
return AXObject::elementAccessibilityHitTest(point);
}
//
// High-level accessibility tree access.
//
AXObject* AXLayoutObject::computeParent() const {
ASSERT(!isDetached());
if (!m_layoutObject)
return 0;
if (ariaRoleAttribute() == MenuBarRole)
return axObjectCache().getOrCreate(m_layoutObject->parent());
// menuButton and its corresponding menu are DOM siblings, but Accessibility
// needs them to be parent/child.
if (ariaRoleAttribute() == MenuRole) {
AXObject* parent = menuButtonForMenu();
if (parent)
return parent;
}
LayoutObject* parentObj = layoutParentObject();
if (parentObj)
return axObjectCache().getOrCreate(parentObj);
// A WebArea's parent should be the page popup owner, if any, otherwise null.
if (isWebArea()) {
LocalFrame* frame = m_layoutObject->frame();
return axObjectCache().getOrCreate(frame->pagePopupOwner());
}
return 0;
}
AXObject* AXLayoutObject::computeParentIfExists() const {
if (!m_layoutObject)
return 0;
if (ariaRoleAttribute() == MenuBarRole)
return axObjectCache().get(m_layoutObject->parent());
// menuButton and its corresponding menu are DOM siblings, but Accessibility
// needs them to be parent/child.
if (ariaRoleAttribute() == MenuRole) {
AXObject* parent = menuButtonForMenu();
if (parent)
return parent;
}
LayoutObject* parentObj = layoutParentObject();
if (parentObj)
return axObjectCache().get(parentObj);
// A WebArea's parent should be the page popup owner, if any, otherwise null.
if (isWebArea()) {
LocalFrame* frame = m_layoutObject->frame();
return axObjectCache().get(frame->pagePopupOwner());
}
return 0;
}
//
// Low-level accessibility tree exploration, only for use within the
// accessibility module.
//
AXObject* AXLayoutObject::rawFirstChild() const {
if (!m_layoutObject)
return 0;
LayoutObject* firstChild = firstChildConsideringContinuation(m_layoutObject);
if (!firstChild)
return 0;
return axObjectCache().getOrCreate(firstChild);
}
AXObject* AXLayoutObject::rawNextSibling() const {
if (!m_layoutObject)
return 0;
LayoutObject* nextSibling = 0;
LayoutInline* inlineContinuation =
m_layoutObject->isLayoutBlockFlow()
? toLayoutBlockFlow(m_layoutObject)->inlineElementContinuation()
: nullptr;
if (inlineContinuation) {
// Case 1: node is a block and has an inline continuation. Next sibling is
// the inline continuation's first child.
nextSibling = firstChildConsideringContinuation(inlineContinuation);
} else if (m_layoutObject->isAnonymousBlock() &&
lastChildHasContinuation(m_layoutObject)) {
// Case 2: Anonymous block parent of the start of a continuation - skip all
// the way to after the parent of the end, since everything in between will
// be linked up via the continuation.
LayoutObject* lastParent =
endOfContinuations(toLayoutBlock(m_layoutObject)->lastChild())
->parent();
while (lastChildHasContinuation(lastParent))
lastParent = endOfContinuations(lastParent->slowLastChild())->parent();
nextSibling = lastParent->nextSibling();
} else if (LayoutObject* ns = m_layoutObject->nextSibling()) {
// Case 3: node has an actual next sibling
nextSibling = ns;
} else if (isInlineWithContinuation(m_layoutObject)) {
// Case 4: node is an inline with a continuation. Next sibling is the next
// sibling of the end of the continuation chain.
nextSibling = endOfContinuations(m_layoutObject)->nextSibling();
} else if (m_layoutObject->parent() &&
isInlineWithContinuation(m_layoutObject->parent())) {
// Case 5: node has no next sibling, and its parent is an inline with a
// continuation.
LayoutObject* continuation =
toLayoutInline(m_layoutObject->parent())->continuation();
if (continuation->isLayoutBlock()) {
// Case 5a: continuation is a block - in this case the block itself is the
// next sibling.
nextSibling = continuation;
} else {
// Case 5b: continuation is an inline - in this case the inline's first
// child is the next sibling.
nextSibling = firstChildConsideringContinuation(continuation);
}
}
if (!nextSibling)
return 0;
return axObjectCache().getOrCreate(nextSibling);
}
void AXLayoutObject::addChildren() {
ASSERT(!isDetached());
// 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);
m_haveChildren = true;
if (!canHaveChildren())
return;
HeapVector<Member<AXObject>> ownedChildren;
computeAriaOwnsChildren(ownedChildren);
for (AXObject* obj = rawFirstChild(); obj; obj = obj->rawNextSibling()) {
if (!axObjectCache().isAriaOwned(obj)) {
obj->setParent(this);
addChild(obj);
}
}
addHiddenChildren();
addPopupChildren();
addImageMapChildren();
addTextFieldChildren();
addCanvasChildren();
addRemoteSVGChildren();
addInlineTextBoxChildren(false);
for (const auto& child : m_children) {
if (!child->cachedParentObject())
child->setParent(this);
}
for (const auto& ownedChild : ownedChildren)
addChild(ownedChild);
}
bool AXLayoutObject::canHaveChildren() const {
if (!m_layoutObject)
return false;
return AXNodeObject::canHaveChildren();
}
void AXLayoutObject::updateChildrenIfNecessary() {
if (needsToUpdateChildren())
clearChildren();
AXObject::updateChildrenIfNecessary();
}
void AXLayoutObject::clearChildren() {
AXObject::clearChildren();
m_childrenDirty = false;
}
//
// Properties of the object's owning document or page.
//
double AXLayoutObject::estimatedLoadingProgress() const {
if (!m_layoutObject)
return 0;
if (isLoaded())
return 1.0;
if (LocalFrame* frame = m_layoutObject->document().frame())
return frame->loader().progress().estimatedProgress();
return 0;
}
//
// DOM and layout tree access.
//
Node* AXLayoutObject::getNode() const {
return getLayoutObject() ? getLayoutObject()->node() : nullptr;
}
Document* AXLayoutObject::getDocument() const {
if (!getLayoutObject())
return nullptr;
return &getLayoutObject()->document();
}
FrameView* AXLayoutObject::documentFrameView() const {
if (!getLayoutObject())
return nullptr;
// this is the LayoutObject's Document's LocalFrame's FrameView
return getLayoutObject()->document().view();
}
Element* AXLayoutObject::anchorElement() const {
if (!m_layoutObject)
return 0;
AXObjectCacheImpl& cache = axObjectCache();
LayoutObject* currLayoutObject;
// Search up the layout tree for a LayoutObject with a DOM node. Defer to an
// earlier continuation, though.
for (currLayoutObject = m_layoutObject;
currLayoutObject && !currLayoutObject->node();
currLayoutObject = currLayoutObject->parent()) {
if (currLayoutObject->isAnonymousBlock() &&
currLayoutObject->isLayoutBlockFlow()) {
LayoutObject* continuation =
toLayoutBlockFlow(currLayoutObject)->continuation();
if (continuation)
return cache.getOrCreate(continuation)->anchorElement();
}
}
// bail if none found
if (!currLayoutObject)
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 = currLayoutObject->node();
if (!node)
return nullptr;
for (Node& runner : NodeTraversal::inclusiveAncestorsOf(*node)) {
if (isHTMLAnchorElement(runner) ||
(runner.layoutObject() &&
cache.getOrCreate(runner.layoutObject())->isAnchor()))
return toElement(&runner);
}
return 0;
}
//
// Functions that retrieve the current selection.
//
AXObject::AXRange AXLayoutObject::selection() const {
AXRange textSelection = textControlSelection();
if (textSelection.isValid())
return textSelection;
if (!getLayoutObject() || !getLayoutObject()->frame())
return AXRange();
VisibleSelection selection =
getLayoutObject()
->frame()
->selection()
.computeVisibleSelectionInDOMTreeDeprecated();
if (selection.isNone())
return AXRange();
VisiblePosition visibleStart = selection.visibleStart();
Position start = visibleStart.toParentAnchoredPosition();
TextAffinity startAffinity = visibleStart.affinity();
VisiblePosition visibleEnd = selection.visibleEnd();
Position end = visibleEnd.toParentAnchoredPosition();
TextAffinity endAffinity = visibleEnd.affinity();
Node* anchorNode = start.anchorNode();
ASSERT(anchorNode);
AXLayoutObject* anchorObject = nullptr;
// Find the closest node that has a corresponding AXObject.
// This is because some nodes may be aria hidden or might not even have
// a layout object if they are part of the shadow DOM.
while (anchorNode) {
anchorObject = getUnignoredObjectFromNode(*anchorNode);
if (anchorObject)
break;
if (anchorNode->nextSibling())
anchorNode = anchorNode->nextSibling();
else
anchorNode = anchorNode->parentNode();
}
Node* focusNode = end.anchorNode();
ASSERT(focusNode);
AXLayoutObject* focusObject = nullptr;
while (focusNode) {
focusObject = getUnignoredObjectFromNode(*focusNode);
if (focusObject)
break;
if (focusNode->previousSibling())
focusNode = focusNode->previousSibling();
else
focusNode = focusNode->parentNode();
}
if (!anchorObject || !focusObject)
return AXRange();
int anchorOffset = anchorObject->indexForVisiblePosition(visibleStart);
ASSERT(anchorOffset >= 0);
int focusOffset = focusObject->indexForVisiblePosition(visibleEnd);
ASSERT(focusOffset >= 0);
return AXRange(anchorObject, anchorOffset, startAffinity, focusObject,
focusOffset, endAffinity);
}
// Gets only the start and end offsets of the selection computed using the
// current object as the starting point. Returns a null selection if there is
// no selection in the subtree rooted at this object.
AXObject::AXRange AXLayoutObject::selectionUnderObject() const {
AXRange textSelection = textControlSelection();
if (textSelection.isValid())
return textSelection;
if (!getNode() || !getLayoutObject()->frame())
return AXRange();
VisibleSelection selection =
getLayoutObject()
->frame()
->selection()
.computeVisibleSelectionInDOMTreeDeprecated();
Range* selectionRange = createRange(firstEphemeralRangeOf(selection));
ContainerNode* parentNode = getNode()->parentNode();
int nodeIndex = getNode()->nodeIndex();
if (!selectionRange
// Selection is contained in node.
||
!(parentNode &&
selectionRange->comparePoint(parentNode, nodeIndex,
IGNORE_EXCEPTION_FOR_TESTING) < 0 &&
selectionRange->comparePoint(parentNode, nodeIndex + 1,
IGNORE_EXCEPTION_FOR_TESTING) > 0)) {
return AXRange();
}
int start = indexForVisiblePosition(selection.visibleStart());
ASSERT(start >= 0);
int end = indexForVisiblePosition(selection.visibleEnd());
ASSERT(end >= 0);
return AXRange(start, end);
}
AXObject::AXRange AXLayoutObject::textControlSelection() const {
if (!getLayoutObject())
return AXRange();
LayoutObject* layout = nullptr;
if (getLayoutObject()->isTextControl()) {
layout = getLayoutObject();
} else {
Element* focusedElement = getDocument()->focusedElement();
if (focusedElement && focusedElement->layoutObject() &&
focusedElement->layoutObject()->isTextControl())
layout = focusedElement->layoutObject();
}
if (!layout)
return AXRange();
AXObject* axObject = axObjectCache().getOrCreate(layout);
if (!axObject || !axObject->isAXLayoutObject())
return AXRange();
VisibleSelection selection =
layout->frame()->selection().computeVisibleSelectionInDOMTreeDeprecated();
TextControlElement* textControl =
toLayoutTextControl(layout)->textControlElement();
ASSERT(textControl);
int start = textControl->selectionStart();
int end = textControl->selectionEnd();
return AXRange(axObject, start, selection.visibleStart().affinity(), axObject,
end, selection.visibleEnd().affinity());
}
int AXLayoutObject::indexForVisiblePosition(
const VisiblePosition& position) const {
if (getLayoutObject() && getLayoutObject()->isTextControl()) {
TextControlElement* textControl =
toLayoutTextControl(getLayoutObject())->textControlElement();
return textControl->indexForVisiblePosition(position);
}
if (!getNode())
return 0;
Position indexPosition = position.deepEquivalent();
if (indexPosition.isNull())
return 0;
Range* range = Range::create(*getDocument());
range->setStart(getNode(), 0, IGNORE_EXCEPTION_FOR_TESTING);
range->setEnd(indexPosition, IGNORE_EXCEPTION_FOR_TESTING);
return TextIterator::rangeLength(range->startPosition(),
range->endPosition());
}
AXLayoutObject* AXLayoutObject::getUnignoredObjectFromNode(Node& node) const {
if (isDetached())
return nullptr;
AXObject* axObject = axObjectCache().getOrCreate(&node);
if (!axObject)
return nullptr;
if (axObject->isAXLayoutObject() && !axObject->accessibilityIsIgnored())
return toAXLayoutObject(axObject);
return nullptr;
}
//
// Modify or take an action on an object.
//
// Convert from an accessible object and offset to a VisiblePosition.
static VisiblePosition toVisiblePosition(AXObject* obj, int offset) {
if (!obj->getNode())
return VisiblePosition();
Node* node = obj->getNode();
if (!node->isTextNode()) {
int childCount = obj->children().size();
// Place position immediately before the container node, if there was no
// children.
if (childCount == 0) {
if (!obj->parentObject())
return VisiblePosition();
return toVisiblePosition(obj->parentObject(), obj->indexInParent());
}
// The offsets are child offsets over the AX tree. Note that we allow
// for the offset to equal the number of children as |Range| does.
if (offset < 0 || offset > childCount)
return VisiblePosition();
// Clamp to between 0 and child count - 1.
int clampedOffset =
static_cast<unsigned>(offset) > (obj->children().size() - 1)
? offset - 1
: offset;
AXObject* childObj = obj->children()[clampedOffset];
Node* childNode = childObj->getNode();
if (!childNode || !childNode->parentNode())
return VisiblePosition();
// The index in parent.
int adjustedOffset = childNode->nodeIndex();
// If we had to clamp the offset above, the client wants to select the
// end of the node.
if (clampedOffset != offset)
adjustedOffset++;
return createVisiblePosition(
Position::editingPositionOf(childNode->parentNode(), adjustedOffset));
}
// If it is a text node, we need to call some utility functions that use a
// TextIterator to walk the characters of the node and figure out the position
// corresponding to the visible character at position |offset|.
ContainerNode* parent = node->parentNode();
if (!parent)
return VisiblePosition();
VisiblePosition nodePosition = blink::visiblePositionBeforeNode(*node);
int nodeIndex = blink::indexForVisiblePosition(nodePosition, parent);
return blink::visiblePositionForIndex(nodeIndex + offset, parent);
}
void AXLayoutObject::setSelection(const AXRange& selection) {
if (!getLayoutObject() || !selection.isValid())
return;
AXObject* anchorObject =
selection.anchorObject ? selection.anchorObject.get() : this;
AXObject* focusObject =
selection.focusObject ? selection.focusObject.get() : this;
if (!isValidSelectionBound(anchorObject) ||
!isValidSelectionBound(focusObject)) {
return;
}
// The selection offsets are offsets into the accessible value.
if (anchorObject == focusObject &&
anchorObject->getLayoutObject()->isTextControl()) {
TextControlElement* textControl =
toLayoutTextControl(anchorObject->getLayoutObject())
->textControlElement();
if (selection.anchorOffset <= selection.focusOffset) {
textControl->setSelectionRange(selection.anchorOffset,
selection.focusOffset,
SelectionHasForwardDirection);
} else {
textControl->setSelectionRange(selection.focusOffset,
selection.anchorOffset,
SelectionHasBackwardDirection);
}
return;
}
LocalFrame* frame = getLayoutObject()->frame();
if (!frame)
return;
// TODO(dglazkov): The use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. see http://crbug.com/590369 for more details.
// This callsite should probably move up the stack.
frame->document()->updateStyleAndLayoutIgnorePendingStylesheets();
// Set the selection based on visible positions, because the offsets in
// accessibility nodes are based on visible indexes, which often skips
// redundant whitespace, for example.
VisiblePosition anchorVisiblePosition =
toVisiblePosition(anchorObject, selection.anchorOffset);
VisiblePosition focusVisiblePosition =
toVisiblePosition(focusObject, selection.focusOffset);
if (anchorVisiblePosition.isNull() || focusVisiblePosition.isNull())
return;
frame->selection().setSelection(
SelectionInDOMTree::Builder()
.collapse(anchorVisiblePosition.toPositionWithAffinity())
.extend(focusVisiblePosition.deepEquivalent())
.build());
}
bool AXLayoutObject::isValidSelectionBound(const AXObject* boundObject) const {
return getLayoutObject() && boundObject && !boundObject->isDetached() &&
boundObject->isAXLayoutObject() && boundObject->getLayoutObject() &&
boundObject->getLayoutObject()->frame() ==
getLayoutObject()->frame() &&
&boundObject->axObjectCache() == &axObjectCache();
}
void AXLayoutObject::setValue(const String& string) {
if (!getNode() || !getNode()->isElementNode())
return;
if (!m_layoutObject || !m_layoutObject->isBoxModelObject())
return;
LayoutBoxModelObject* layoutObject = toLayoutBoxModelObject(m_layoutObject);
if (layoutObject->isTextField() && isHTMLInputElement(*getNode()))
toHTMLInputElement(*getNode())
.setValue(string, DispatchInputAndChangeEvent);
else if (layoutObject->isTextArea() && isHTMLTextAreaElement(*getNode()))
toHTMLTextAreaElement(*getNode())
.setValue(string, DispatchInputAndChangeEvent);
}
//
// Notifications that this object may have changed.
//
void AXLayoutObject::handleActiveDescendantChanged() {
if (!getLayoutObject())
return;
AXObject* focusedObject = axObjectCache().focusedObject();
if (focusedObject == this && supportsActiveDescendant()) {
axObjectCache().postNotification(
getLayoutObject(), AXObjectCacheImpl::AXActiveDescendantChanged);
}
}
void AXLayoutObject::handleAriaExpandedChanged() {
// Find if a parent of this object should handle aria-expanded changes.
AXObject* containerParent = this->parentObject();
while (containerParent) {
bool foundParent = false;
switch (containerParent->roleValue()) {
case TreeRole:
case TreeGridRole:
case GridRole:
case TableRole:
foundParent = true;
break;
default:
break;
}
if (foundParent)
break;
containerParent = containerParent->parentObject();
}
// Post that the row count changed.
if (containerParent)
axObjectCache().postNotification(containerParent,
AXObjectCacheImpl::AXRowCountChanged);
// Post that the specific row either collapsed or expanded.
AccessibilityExpanded expanded = isExpanded();
if (!expanded)
return;
if (roleValue() == RowRole || roleValue() == TreeItemRole) {
AXObjectCacheImpl::AXNotification notification =
AXObjectCacheImpl::AXRowExpanded;
if (expanded == ExpandedCollapsed)
notification = AXObjectCacheImpl::AXRowCollapsed;
axObjectCache().postNotification(this, notification);
} else {
axObjectCache().postNotification(this,
AXObjectCacheImpl::AXExpandedChanged);
}
}
void AXLayoutObject::textChanged() {
if (!m_layoutObject)
return;
Settings* settings = getDocument()->settings();
if (settings && settings->getInlineTextBoxAccessibilityEnabled() &&
roleValue() == StaticTextRole)
childrenChanged();
// Do this last - AXNodeObject::textChanged posts live region announcements,
// and we should update the inline text boxes first.
AXNodeObject::textChanged();
}
//
// Text metrics. Most of these should be deprecated, needs major cleanup.
//
// NOTE: Consider providing this utility method as AX API
int AXLayoutObject::index(const VisiblePosition& position) const {
if (position.isNull() || !isTextControl())
return -1;
if (layoutObjectContainsPosition(m_layoutObject, position.deepEquivalent()))
return indexForVisiblePosition(position);
return -1;
}
VisiblePosition AXLayoutObject::visiblePositionForIndex(int index) const {
if (!m_layoutObject)
return VisiblePosition();
if (m_layoutObject->isTextControl())
return toLayoutTextControl(m_layoutObject)
->textControlElement()
->visiblePositionForIndex(index);
Node* node = m_layoutObject->node();
if (!node)
return VisiblePosition();
if (index <= 0)
return createVisiblePosition(firstPositionInOrBeforeNode(node));
Position start, end;
bool selected = Range::selectNodeContents(node, start, end);
if (!selected)
return VisiblePosition();
CharacterIterator it(start, end);
it.advance(index - 1);
return createVisiblePosition(Position(it.currentContainer(), it.endOffset()),
TextAffinity::Upstream);
}
void AXLayoutObject::addInlineTextBoxChildren(bool force) {
Settings* settings = getDocument()->settings();
if (!force &&
(!settings || !settings->getInlineTextBoxAccessibilityEnabled()))
return;
if (!getLayoutObject() || !getLayoutObject()->isText())
return;
if (getLayoutObject()->needsLayout()) {
// If a LayoutText needs layout, its inline text boxes are either
// nonexistent or invalid, so defer until the layout happens and
// the layoutObject calls AXObjectCacheImpl::inlineTextBoxesUpdated.
return;
}
LayoutText* layoutText = toLayoutText(getLayoutObject());
for (RefPtr<AbstractInlineTextBox> box =
layoutText->firstAbstractInlineTextBox();
box.get(); box = box->nextInlineTextBox()) {
AXObject* axObject = axObjectCache().getOrCreate(box.get());
if (!axObject->accessibilityIsIgnored())
m_children.push_back(axObject);
}
}
void AXLayoutObject::lineBreaks(Vector<int>& lineBreaks) const {
if (!isTextControl())
return;
VisiblePosition visiblePos = visiblePositionForIndex(0);
VisiblePosition prevVisiblePos = visiblePos;
visiblePos = nextLinePosition(visiblePos, LayoutUnit(), HasEditableAXRole);
// nextLinePosition moves to the end of the current line when there are
// no more lines.
while (visiblePos.isNotNull() && !inSameLine(prevVisiblePos, visiblePos)) {
lineBreaks.push_back(indexForVisiblePosition(visiblePos));
prevVisiblePos = visiblePos;
visiblePos = nextLinePosition(visiblePos, LayoutUnit(), HasEditableAXRole);
// Make sure we always make forward progress.
if (visiblePos.deepEquivalent().compareTo(prevVisiblePos.deepEquivalent()) <
0)
break;
}
}
//
// Private.
//
AXObject* AXLayoutObject::treeAncestorDisallowingChild() const {
// Determine if this is in a tree. If so, we apply special behavior to make it
// work like an AXOutline.
AXObject* axObj = parentObject();
AXObject* treeAncestor = 0;
while (axObj) {
if (axObj->isTree()) {
treeAncestor = axObj;
break;
}
axObj = axObj->parentObject();
}
// If the object is in a tree, only tree items should be exposed (and the
// children of tree items).
if (treeAncestor) {
AccessibilityRole role = roleValue();
if (role != TreeItemRole && role != StaticTextRole)
return treeAncestor;
}
return 0;
}
bool AXLayoutObject::isTabItemSelected() const {
if (!isTabItem() || !getLayoutObject())
return false;
Node* node = getNode();
if (!node || !node->isElementNode())
return false;
// The ARIA spec says a tab item can also be selected if it is aria-labeled by
// a tabpanel that has keyboard focus inside of it, or if a tabpanel in its
// aria-controls list has KB focus inside of it.
AXObject* focusedElement = axObjectCache().focusedObject();
if (!focusedElement)
return false;
HeapVector<Member<Element>> elements;
elementsFromAttribute(elements, aria_controlsAttr);
for (const auto& element : elements) {
AXObject* tabPanel = axObjectCache().getOrCreate(element);
// A tab item should only control tab panels.
if (!tabPanel || tabPanel->roleValue() != TabPanelRole)
continue;
AXObject* checkFocusElement = focusedElement;
// Check if the focused element is a descendant of the element controlled by
// the tab item.
while (checkFocusElement) {
if (tabPanel == checkFocusElement)
return true;
checkFocusElement = checkFocusElement->parentObject();
}
}
return false;
}
AXObject* AXLayoutObject::accessibilityImageMapHitTest(
HTMLAreaElement* area,
const IntPoint& point) const {
if (!area)
return 0;
AXObject* parent = axObjectCache().getOrCreate(area->imageElement());
if (!parent)
return 0;
for (const auto& child : parent->children()) {
if (child->getBoundsInFrameCoordinates().contains(point))
return child.get();
}
return 0;
}
LayoutObject* AXLayoutObject::layoutParentObject() const {
if (!m_layoutObject)
return 0;
LayoutObject* startOfConts = m_layoutObject->isLayoutBlockFlow()
? startOfContinuations(m_layoutObject)
: nullptr;
if (startOfConts) {
// Case 1: node is a block and is an inline's continuation. Parent
// is the start of the continuation chain.
return startOfConts;
}
LayoutObject* parent = m_layoutObject->parent();
startOfConts =
parent && parent->isLayoutInline() ? startOfContinuations(parent) : 0;
if (startOfConts) {
// Case 2: node's parent is an inline which is some node's continuation;
// parent is the earliest node in the continuation chain.
return startOfConts;
}
LayoutObject* firstChild = parent ? parent->slowFirstChild() : 0;
if (firstChild && firstChild->node()) {
// Case 3: The first sibling is the beginning of a continuation chain. Find
// the origin of that continuation. Get the node's layoutObject and follow
// that continuation chain until the first child is found.
for (LayoutObject* nodeLayoutFirstChild =
firstChild->node()->layoutObject();
nodeLayoutFirstChild != firstChild;
nodeLayoutFirstChild = firstChild->node()->layoutObject()) {
for (LayoutObject* contsTest = nodeLayoutFirstChild; contsTest;
contsTest = nextContinuation(contsTest)) {
if (contsTest == firstChild) {
parent = nodeLayoutFirstChild->parent();
break;
}
}
LayoutObject* newFirstChild = parent->slowFirstChild();
if (firstChild == newFirstChild)
break;
firstChild = newFirstChild;
if (!firstChild->node())
break;
}
}
return parent;
}
bool AXLayoutObject::isSVGImage() const {
return remoteSVGRootElement();
}
void AXLayoutObject::detachRemoteSVGRoot() {
if (AXSVGRoot* root = remoteSVGRootElement())
root->setParent(0);
}
AXSVGRoot* AXLayoutObject::remoteSVGRootElement() const {
// FIXME(dmazzoni): none of this code properly handled multiple references to
// the same remote SVG document. I'm disabling this support until it can be
// fixed properly.
return 0;
}
AXObject* AXLayoutObject::remoteSVGElementHitTest(const IntPoint& point) const {
AXObject* remote = remoteSVGRootElement();
if (!remote)
return 0;
IntSize offset =
point - roundedIntPoint(getBoundsInFrameCoordinates().location());
return remote->accessibilityHitTest(IntPoint(offset));
}
// The boundingBox for elements within the remote SVG element needs to be offset
// by its position within the parent page, otherwise they are in relative
// coordinates only.
void AXLayoutObject::offsetBoundingBoxForRemoteSVGElement(
LayoutRect& rect) const {
for (AXObject* parent = parentObject(); parent;
parent = parent->parentObject()) {
if (parent->isAXSVGRoot()) {
rect.moveBy(
parent->parentObject()->getBoundsInFrameCoordinates().location());
break;
}
}
}
// Hidden children are those that are not laid out or visible, but are
// specifically marked as aria-hidden=false,
// meaning that they should be exposed to the AX hierarchy.
void AXLayoutObject::addHiddenChildren() {
Node* node = this->getNode();
if (!node)
return;
// First do a quick run through to determine if we have any hidden nodes (most
// often we will not). If we do have hidden nodes, we need to determine where
// to insert them so they match DOM order as close as possible.
bool shouldInsertHiddenNodes = false;
for (Node& child : NodeTraversal::childrenOf(*node)) {
if (!child.layoutObject() && isNodeAriaVisible(&child)) {
shouldInsertHiddenNodes = true;
break;
}
}
if (!shouldInsertHiddenNodes)
return;
// Iterate through all of the children, including those that may have already
// been added, and try to insert hidden nodes in the correct place in the DOM
// order.
unsigned insertionIndex = 0;
for (Node& child : NodeTraversal::childrenOf(*node)) {
if (child.layoutObject()) {
// Find out where the last layout sibling is located within m_children.
if (AXObject* childObject = axObjectCache().get(child.layoutObject())) {
if (childObject->accessibilityIsIgnored()) {
const auto& children = childObject->children();
childObject = children.size() ? children.back().get() : 0;
}
if (childObject)
insertionIndex = m_children.find(childObject) + 1;
continue;
}
}
if (!isNodeAriaVisible(&child))
continue;
unsigned previousSize = m_children.size();
if (insertionIndex > previousSize)
insertionIndex = previousSize;
insertChild(axObjectCache().getOrCreate(&child), insertionIndex);
insertionIndex += (m_children.size() - previousSize);
}
}
void AXLayoutObject::addTextFieldChildren() {
Node* node = this->getNode();
if (!isHTMLInputElement(node))
return;
HTMLInputElement& input = toHTMLInputElement(*node);
Element* spinButtonElement = input.userAgentShadowRoot()->getElementById(
ShadowElementNames::spinButton());
if (!spinButtonElement || !spinButtonElement->isSpinButtonElement())
return;
AXSpinButton* axSpinButton =
toAXSpinButton(axObjectCache().getOrCreate(SpinButtonRole));
axSpinButton->setSpinButtonElement(toSpinButtonElement(spinButtonElement));
axSpinButton->setParent(this);
m_children.push_back(axSpinButton);
}
void AXLayoutObject::addImageMapChildren() {
LayoutBoxModelObject* cssBox = getLayoutBoxModelObject();
if (!cssBox || !cssBox->isLayoutImage())
return;
HTMLMapElement* map = toLayoutImage(cssBox)->imageMap();
if (!map)
return;
for (HTMLAreaElement& area :
Traversal<HTMLAreaElement>::descendantsOf(*map)) {
// add an <area> element for this child if it has a link
AXObject* obj = axObjectCache().getOrCreate(&area);
if (obj) {
AXImageMapLink* areaObject = toAXImageMapLink(obj);
areaObject->setParent(this);
ASSERT(areaObject->axObjectID() != 0);
if (!areaObject->accessibilityIsIgnored())
m_children.push_back(areaObject);
else
axObjectCache().remove(areaObject->axObjectID());
}
}
}
void AXLayoutObject::addCanvasChildren() {
if (!isHTMLCanvasElement(getNode()))
return;
// If it's a canvas, it won't have laid out children, but it might have
// accessible fallback content. Clear m_haveChildren because
// AXNodeObject::addChildren will expect it to be false.
ASSERT(!m_children.size());
m_haveChildren = false;
AXNodeObject::addChildren();
}
void AXLayoutObject::addPopupChildren() {
if (!isHTMLInputElement(getNode()))
return;
if (AXObject* axPopup = toHTMLInputElement(getNode())->popupRootAXObject())
m_children.push_back(axPopup);
}
void AXLayoutObject::addRemoteSVGChildren() {
AXSVGRoot* root = remoteSVGRootElement();
if (!root)
return;
root->setParent(this);
if (root->accessibilityIsIgnored()) {
for (const auto& child : root->children())
m_children.push_back(child);
} else {
m_children.push_back(root);
}
}
bool AXLayoutObject::elementAttributeValue(
const QualifiedName& attributeName) const {
if (!m_layoutObject)
return false;
return equalIgnoringCase(getAttribute(attributeName), "true");
}
} // namespace blink