blob: 01d1c1ed6adb7f6e6668024e8711f66ba6efd1f6 [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/AccessibleNode.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 "platform/wtf/StdLibExtras.h"
using blink::WebLocalizedString;
namespace blink {
using namespace HTMLNames;
static inline LayoutObject* FirstChildInContinuation(
const LayoutInline& layout_object) {
LayoutBoxModelObject* r = layout_object.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* layout_object = ToLayoutBoxModelObject(object);
if (!layout_object->IsLayoutInline())
return false;
return ToLayoutInline(layout_object)->Continuation();
}
static inline LayoutObject* FirstChildConsideringContinuation(
LayoutObject* layout_object) {
LayoutObject* first_child = layout_object->SlowFirstChild();
// CSS first-letter pseudo element is handled as continuation. Returning it
// will result in duplicated elements.
if (first_child && first_child->IsText() &&
ToLayoutText(first_child)->IsTextFragment() &&
ToLayoutTextFragment(first_child)->GetFirstLetterPseudoElement())
return nullptr;
if (!first_child && IsInlineWithContinuation(layout_object))
first_child = FirstChildInContinuation(ToLayoutInline(*layout_object));
return first_child;
}
static inline LayoutInline* StartOfContinuations(LayoutObject* r) {
if (r->IsInlineElementContinuation()) {
return ToLayoutInline(r->GetNode()->GetLayoutObject());
}
// Blocks with a previous continuation always have a next continuation
if (r->IsLayoutBlockFlow() &&
ToLayoutBlockFlow(r)->InlineElementContinuation())
return ToLayoutInline(ToLayoutBlockFlow(r)
->InlineElementContinuation()
->GetNode()
->GetLayoutObject());
return 0;
}
static inline LayoutObject* EndOfContinuations(LayoutObject* layout_object) {
LayoutObject* prev = layout_object;
LayoutObject* cur = layout_object;
if (!cur->IsLayoutInline() && !cur->IsLayoutBlockFlow())
return layout_object;
while (cur) {
prev = cur;
if (cur->IsLayoutInline()) {
cur = ToLayoutInline(cur)->InlineElementContinuation();
DCHECK(cur || !ToLayoutInline(prev)->Continuation());
} else {
cur = ToLayoutBlockFlow(cur)->InlineElementContinuation();
}
}
return prev;
}
static inline bool LastChildHasContinuation(LayoutObject* layout_object) {
LayoutObject* last_child = layout_object->SlowLastChild();
return last_child && IsInlineWithContinuation(last_child);
}
static LayoutBoxModelObject* NextContinuation(LayoutObject* layout_object) {
DCHECK(layout_object);
if (layout_object->IsLayoutInline() && !layout_object->IsAtomicInlineLevel())
return ToLayoutInline(layout_object)->Continuation();
if (layout_object->IsLayoutBlockFlow())
return ToLayoutBlockFlow(layout_object)->InlineElementContinuation();
return 0;
}
AXLayoutObject::AXLayoutObject(LayoutObject* layout_object,
AXObjectCacheImpl& ax_object_cache)
: AXNodeObject(layout_object->GetNode(), ax_object_cache),
layout_object_(layout_object) {
#if DCHECK_IS_ON()
layout_object_->SetHasAXObject(true);
#endif
}
AXLayoutObject* AXLayoutObject::Create(LayoutObject* layout_object,
AXObjectCacheImpl& ax_object_cache) {
return new AXLayoutObject(layout_object, ax_object_cache);
}
AXLayoutObject::~AXLayoutObject() {
DCHECK(IsDetached());
}
LayoutBoxModelObject* AXLayoutObject::GetLayoutBoxModelObject() const {
if (!layout_object_ || !layout_object_->IsBoxModelObject())
return 0;
return ToLayoutBoxModelObject(layout_object_);
}
ScrollableArea* AXLayoutObject::GetScrollableAreaIfScrollable() const {
if (IsWebArea())
return DocumentFrameView()->LayoutViewportScrollableArea();
if (!layout_object_ || !layout_object_->IsBox())
return 0;
LayoutBox* box = ToLayoutBox(layout_object_);
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 = layout_object_->GetNode();
LayoutBoxModelObject* css_box = GetLayoutBoxModelObject();
if ((css_box && css_box->IsListItem()) || isHTMLLIElement(node))
return kListItemRole;
if (layout_object_->IsListMarker())
return kListMarkerRole;
if (layout_object_->IsBR())
return kLineBreakRole;
if (layout_object_->IsText())
return kStaticTextRole;
if (css_box && IsImageOrAltText(css_box, node)) {
if (node && node->IsLink())
return kImageMapRole;
if (isHTMLInputElement(node))
return AriaHasPopup() ? kPopUpButtonRole : kButtonRole;
if (IsSVGImage())
return kSVGRootRole;
return kImageRole;
}
// Note: if JavaScript is disabled, the layoutObject won't be a
// LayoutHTMLCanvas.
if (isHTMLCanvasElement(node) && layout_object_->IsCanvas())
return kCanvasRole;
if (css_box && css_box->IsLayoutView())
return kWebAreaRole;
if (layout_object_->IsSVGImage())
return kImageRole;
if (layout_object_->IsSVGRoot())
return kSVGRootRole;
// Table sections should be ignored.
if (layout_object_->IsTableSection())
return kIgnoredRole;
if (layout_object_->IsHR())
return kSplitterRole;
return AXNodeObject::NativeAccessibilityRoleIgnoringAria();
}
AccessibilityRole AXLayoutObject::DetermineAccessibilityRole() {
if (!layout_object_)
return kUnknownRole;
if ((aria_role_ = DetermineAriaRoleAttribute()) != kUnknownRole)
return aria_role_;
AccessibilityRole role = NativeAccessibilityRoleIgnoringAria();
if (role != kUnknownRole)
return role;
if (layout_object_->IsLayoutBlockFlow())
return kGroupRole;
// If the element does not have role, but it has ARIA attributes or is an
// in-page link target, accessibility should fallback to exposing it as a
// group.
if (IsInPageLinkTarget() || SupportsARIAAttributes())
return kGroupRole;
return kUnknownRole;
}
void AXLayoutObject::Init() {
AXNodeObject::Init();
}
void AXLayoutObject::Detach() {
AXNodeObject::Detach();
DetachRemoteSVGRoot();
#if DCHECK_IS_ON()
if (layout_object_)
layout_object_->SetHasAXObject(false);
#endif
layout_object_ = 0;
}
//
// Check object role or purpose.
//
static bool IsLinkable(const AXObjectImpl& 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()->GetDocument();
HTMLElement* body = document.body();
if (body && HasEditableStyle(*body)) {
AXObjectImpl* ax_body = AxObjectCache().GetOrCreate(body);
return ax_body && ax_body != ax_body->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 = layout_object_->GetDocument();
HTMLElement* body = document.body();
if (body && HasRichlyEditableStyle(*body)) {
AXObjectImpl* ax_body = AxObjectCache().GetOrCreate(body);
return ax_body && ax_body != ax_body->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 !layout_object_->GetDocument().Parser();
}
bool AXLayoutObject::IsOffScreen() const {
DCHECK(layout_object_);
IntRect content_rect =
PixelSnappedIntRect(layout_object_->AbsoluteVisualRect());
FrameView* view = layout_object_->GetFrame()->View();
IntRect view_rect = view->VisibleContentRect();
view_rect.Intersect(content_rect);
return view_rect.IsEmpty();
}
bool AXLayoutObject::IsReadOnly() const {
DCHECK(layout_object_);
if (IsWebArea()) {
Document& document = layout_object_->GetDocument();
HTMLElement* body = document.body();
if (body && HasEditableStyle(*body)) {
AXObjectImpl* ax_body = AxObjectCache().GetOrCreate(body);
return !ax_body || ax_body == ax_body->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 layout_object_->Style()->IsLink() &&
layout_object_->Style()->InsideLink() ==
EInsideLink::kInsideVisitedLink;
}
//
// Check object state.
//
bool AXLayoutObject::IsFocused() const {
if (!GetDocument())
return false;
Element* focused_element = GetDocument()->FocusedElement();
if (!focused_element)
return false;
AXObjectImpl* focused_object = AxObjectCache().GetOrCreate(focused_element);
if (!focused_object || !focused_object->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 (focused_object == this ||
(RoleValue() == kWebAreaRole &&
GetDocument()->GetFrame()->Selection().FrameIsFocusedAndActive()))
return true;
return false;
}
bool AXLayoutObject::IsSelected() const {
if (!GetLayoutObject() || !GetNode())
return false;
const AtomicString& aria_selected = GetAttribute(aria_selectedAttr);
if (EqualIgnoringASCIICase(aria_selected, "true"))
return true;
AXObjectImpl* focused_object = AxObjectCache().FocusedObject();
if (AriaRoleAttribute() == kListBoxOptionRole && focused_object &&
focused_object->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* ignored_reasons) const {
// The following cases can apply to any element that's a subclass of
// AXLayoutObject.
if (!layout_object_) {
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
return kIgnoreObject;
}
if (layout_object_->Style()->Visibility() != EVisibility::kVisible) {
// aria-hidden is meant to override visibility as the determinant in AX
// hierarchy inclusion.
if (EqualIgnoringASCIICase(GetAttribute(aria_hiddenAttr), "false"))
return kDefaultBehavior;
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXNotVisible));
return kIgnoreObject;
}
return AXObjectImpl::DefaultObjectInclusion(ignored_reasons);
}
bool AXLayoutObject::ComputeAccessibilityIsIgnored(
IgnoredReasons* ignored_reasons) const {
#if DCHECK_IS_ON()
DCHECK(initialized_);
#endif
if (!layout_object_)
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(ignored_reasons);
if (decision == kIncludeObject)
return false;
if (decision == kIgnoreObject)
return true;
if (layout_object_->IsAnonymousBlock())
return true;
// If this element is within a parent that cannot have children, it should not
// be exposed.
if (IsDescendantOfLeafNode()) {
if (ignored_reasons)
ignored_reasons->push_back(
IgnoredReason(kAXAncestorIsLeafNode, LeafNodeAncestor()));
return true;
}
if (RoleValue() == kIgnoredRole) {
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
return true;
}
if (HasInheritedPresentationalRole()) {
if (ignored_reasons) {
const AXObjectImpl* inherits_from = InheritsPresentationalRoleFrom();
if (inherits_from == this)
ignored_reasons->push_back(IgnoredReason(kAXPresentationalRole));
else
ignored_reasons->push_back(
IgnoredReason(kAXInheritsPresentation, inherits_from));
}
return true;
}
// An ARIA tree can only have tree items and static text as children.
if (AXObjectImpl* tree_ancestor = TreeAncestorDisallowingChild()) {
if (ignored_reasons)
ignored_reasons->push_back(
IgnoredReason(kAXAncestorDisallowsChild, tree_ancestor));
return true;
}
// A LayoutPart is an iframe element or embedded object element or something
// like that. We don't want to ignore those.
if (layout_object_->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.
AXObjectImpl* control_object = CorrespondingControlForLabelElement();
if (control_object && control_object->IsCheckboxOrRadio() &&
control_object->NameFromLabelElement()) {
if (ignored_reasons) {
HTMLLabelElement* label = LabelElementContainer();
if (label && label != GetNode()) {
AXObjectImpl* label_ax_object = AxObjectCache().GetOrCreate(label);
ignored_reasons->push_back(
IgnoredReason(kAXLabelContainer, label_ax_object));
}
ignored_reasons->push_back(IgnoredReason(kAXLabelFor, control_object));
}
return true;
}
if (layout_object_->IsBR())
return false;
if (IsLink())
return false;
if (IsInPageLinkTarget())
return false;
if (layout_object_->IsText()) {
// Static text beneath MenuItems and MenuButtons are just reported along
// with the menu item, so it's ignored on an individual level.
AXObjectImpl* parent = ParentObjectUnignored();
if (parent && (parent->AriaRoleAttribute() == kMenuItemRole ||
parent->AriaRoleAttribute() == kMenuButtonRole)) {
if (ignored_reasons)
ignored_reasons->push_back(
IgnoredReason(kAXStaticTextUsedAsNameFor, parent));
return true;
}
LayoutText* layout_text = ToLayoutText(layout_object_);
if (!layout_text->HasTextBoxes()) {
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXEmptyText));
return true;
}
// Don't ignore static text in editable text controls.
for (AXObjectImpl* parent = ParentObject(); parent;
parent = parent->ParentObject()) {
if (parent->RoleValue() == kTextFieldRole)
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 (layout_text->GetText().Impl()->ContainsOnlyWhitespace()) {
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXEmptyText));
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() != kUnknownRole)
return false;
// don't ignore labels, because they serve as TitleUIElements
Node* node = layout_object_->GetNode();
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() == kAbbrRole)
return false;
// List items play an important role in defining the structure of lists. They
// should not be ignored.
if (RoleValue() == kListItemRole)
return false;
if (RoleValue() == kBlockquoteRole)
return false;
if (RoleValue() == kDialogRole)
return false;
if (RoleValue() == kFigcaptionRole)
return false;
if (RoleValue() == kFigureRole)
return false;
if (RoleValue() == kDetailsRole)
return false;
if (RoleValue() == kMarkRole)
return false;
if (RoleValue() == kMathRole)
return false;
if (RoleValue() == kMeterRole)
return false;
if (RoleValue() == kRubyRole)
return false;
if (RoleValue() == kSplitterRole)
return false;
if (RoleValue() == kTimeRole)
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 (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
return true;
}
if (IsImage())
return false;
if (IsCanvas()) {
if (CanvasHasFallbackContent())
return false;
LayoutHTMLCanvas* canvas = ToLayoutHTMLCanvas(layout_object_);
if (canvas->Size().Height() <= 1 || canvas->Size().Width() <= 1) {
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXProbablyPresentational));
return true;
}
// Otherwise fall through; use presence of help text, title, or description
// to decide.
}
if (IsWebArea() || layout_object_->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 (layout_object_->IsLayoutBlockFlow() && layout_object_->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 has_any_text = !!ToLayoutBlockFlow(layout_object_)->FirstLineBox();
// Always include interesting-looking objects.
if (has_any_text || MouseButtonListener())
return false;
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
return true;
}
// By default, objects should be ignored so that the AX hierarchy is not
// filled with unnecessary items.
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
return true;
}
//
// Properties of static elements.
//
const AtomicString& AXLayoutObject::AccessKey() const {
Node* node = layout_object_->GetNode();
if (!node)
return g_null_atom;
if (!node->IsElementNode())
return g_null_atom;
return ToElement(node)->getAttribute(accesskeyAttr);
}
RGBA32 AXLayoutObject::ComputeBackgroundColor() const {
if (!GetLayoutObject())
return AXNodeObject::BackgroundColor();
Color blended_color = Color::kTransparent;
// Color::blend should be called like this: background.blend(foreground).
for (LayoutObject* layout_object = GetLayoutObject(); layout_object;
layout_object = layout_object->Parent()) {
const AXObjectImpl* ax_parent = AxObjectCache().GetOrCreate(layout_object);
if (ax_parent && ax_parent != this) {
Color parent_color = ax_parent->BackgroundColor();
blended_color = parent_color.Blend(blended_color);
return blended_color.Rgb();
}
const ComputedStyle* style = layout_object->Style();
if (!style || !style->HasBackground())
continue;
Color current_color =
style->VisitedDependentColor(CSSPropertyBackgroundColor);
blended_color = current_color.Blend(blended_color);
// Continue blending until we get no transparency.
if (!blended_color.HasAlpha())
break;
}
// If we still have some transparency, blend in the document base color.
if (blended_color.HasAlpha()) {
FrameView* view = DocumentFrameView();
if (view) {
Color document_base_color = view->BaseBackgroundColor();
blended_color = document_base_color.Blend(blended_color);
} else {
// Default to a white background.
blended_color.BlendWithWhite();
}
}
return blended_color.Rgb();
}
RGBA32 AXLayoutObject::GetColor() const {
if (!GetLayoutObject() || IsColorWell())
return AXNodeObject::GetColor();
const ComputedStyle* style = GetLayoutObject()->Style();
if (!style)
return AXNodeObject::GetColor();
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& font_description =
const_cast<FontDescription&>(style->GetFontDescription());
return font_description.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& max_size) const {
Node* node = GetNode();
if (!node)
return String();
ImageBitmapOptions options;
ImageBitmap* image_bitmap = nullptr;
Document* document = &node->GetDocument();
if (isHTMLImageElement(node)) {
image_bitmap = ImageBitmap::Create(toHTMLImageElement(node),
Optional<IntRect>(), document, options);
} else if (isHTMLCanvasElement(node)) {
image_bitmap = ImageBitmap::Create(toHTMLCanvasElement(node),
Optional<IntRect>(), options);
} else if (isHTMLVideoElement(node)) {
image_bitmap = ImageBitmap::Create(toHTMLVideoElement(node),
Optional<IntRect>(), document, options);
}
if (!image_bitmap)
return String();
RefPtr<StaticBitmapImage> bitmap_image = image_bitmap->BitmapImage();
if (!bitmap_image)
return String();
sk_sp<SkImage> image = bitmap_image->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 x_scale =
max_size.Width() ? max_size.Width() * 1.0 / image->width() : 1.0;
float y_scale =
max_size.Height() ? max_size.Height() * 1.0 / image->height() : 1.0;
float scale = std::min(x_scale, y_scale);
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 row_bytes = info.minRowBytes();
Vector<char> pixel_storage(info.getSafeSize(row_bytes));
SkPixmap pixmap(info, pixel_storage.data(), row_bytes);
if (!SkImage::MakeFromBitmap(bitmap)->readPixels(pixmap, 0, 0))
return String();
// Encode as a PNG and return as a data url.
String data_url =
ImageDataBuffer(
IntSize(width, height),
reinterpret_cast<const unsigned char*>(pixel_storage.data()))
.ToDataURL("image/png", 1.0);
return data_url;
}
String AXLayoutObject::GetText() const {
if (IsPasswordFieldAndShouldHideValue()) {
if (!GetLayoutObject())
return String();
const ComputedStyle* style = GetLayoutObject()->Style();
if (!style)
return String();
unsigned unmasked_text_length = AXNodeObject::GetText().length();
if (!unmasked_text_length)
return String();
UChar mask_character = 0;
switch (style->TextSecurity()) {
case ETextSecurity::kNone:
break; // Fall through to the non-password branch.
case ETextSecurity::kDisc:
mask_character = kBulletCharacter;
break;
case ETextSecurity::kCircle:
mask_character = kWhiteBulletCharacter;
break;
case ETextSecurity::kSquare:
mask_character = kBlackSquareCharacter;
break;
}
if (mask_character) {
StringBuilder masked_text;
masked_text.ReserveCapacity(unmasked_text_length);
for (unsigned i = 0; i < unmasked_text_length; ++i)
masked_text.Append(mask_character);
return masked_text.ToString();
}
}
return AXNodeObject::GetText();
}
AccessibilityTextDirection AXLayoutObject::GetTextDirection() const {
if (!GetLayoutObject())
return AXNodeObject::GetTextDirection();
const ComputedStyle* style = GetLayoutObject()->Style();
if (!style)
return AXNodeObject::GetTextDirection();
if (style->IsHorizontalWritingMode()) {
switch (style->Direction()) {
case TextDirection::kLtr:
return kAccessibilityTextDirectionLTR;
case TextDirection::kRtl:
return kAccessibilityTextDirectionRTL;
}
} else {
switch (style->Direction()) {
case TextDirection::kLtr:
return kAccessibilityTextDirectionTTB;
case TextDirection::kRtl:
return kAccessibilityTextDirectionBTT;
}
}
return AXNodeObject::GetTextDirection();
}
int AXLayoutObject::TextLength() const {
if (!IsTextControl())
return -1;
return GetText().length();
}
TextStyle AXLayoutObject::GetTextStyle() const {
if (!GetLayoutObject())
return AXNodeObject::GetTextStyle();
const ComputedStyle* style = GetLayoutObject()->Style();
if (!style)
return AXNodeObject::GetTextStyle();
unsigned text_style = kTextStyleNone;
if (style->GetFontWeight() == kFontWeightBold)
text_style |= kTextStyleBold;
if (style->GetFontDescription().Style() == kFontStyleItalic)
text_style |= kTextStyleItalic;
if (style->GetTextDecoration() == TextDecoration::kUnderline)
text_style |= kTextStyleUnderline;
if (style->GetTextDecoration() == TextDecoration::kLineThrough)
text_style |= kTextStyleLineThrough;
return static_cast<TextStyle>(text_style);
}
KURL AXLayoutObject::Url() const {
if (IsAnchor() && isHTMLAnchorElement(layout_object_->GetNode())) {
if (HTMLAnchorElement* anchor = toHTMLAnchorElement(AnchorElement()))
return anchor->Href();
}
if (IsWebArea())
return layout_object_->GetDocument().Url();
if (IsImage() && isHTMLImageElement(layout_object_->GetNode()))
return toHTMLImageElement(*layout_object_->GetNode()).Src();
if (IsInputImage())
return toHTMLInputElement(layout_object_->GetNode())->Src();
return KURL();
}
//
// Inline text boxes.
//
void AXLayoutObject::LoadInlineTextBoxes() {
if (!GetLayoutObject())
return;
if (GetLayoutObject()->IsText()) {
ClearChildren();
AddInlineTextBoxChildren(true);
return;
}
for (const auto& child : children_) {
child->LoadInlineTextBoxes();
}
}
AXObjectImpl* AXLayoutObject::NextOnLine() const {
if (!GetLayoutObject())
return nullptr;
AXObjectImpl* result = nullptr;
if (GetLayoutObject()->IsListMarker()) {
AXObjectImpl* next_sibling = RawNextSibling();
if (!next_sibling || !next_sibling->Children().size())
return nullptr;
result = next_sibling->Children()[0].Get();
} else {
InlineBox* inline_box = nullptr;
if (GetLayoutObject()->IsLayoutInline())
inline_box = ToLayoutInline(GetLayoutObject())->LastLineBox();
else if (GetLayoutObject()->IsText())
inline_box = ToLayoutText(GetLayoutObject())->LastTextBox();
if (!inline_box)
return nullptr;
for (InlineBox* next = inline_box->NextOnLine(); next;
next = next->NextOnLine()) {
LayoutObject* layout_object =
LineLayoutAPIShim::LayoutObjectFrom(next->GetLineLayoutItem());
result = AxObjectCache().GetOrCreate(layout_object);
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() == kStaticTextRole &&
result->Children().size())
result = result->Children()[0].Get();
return result;
}
AXObjectImpl* AXLayoutObject::PreviousOnLine() const {
if (!GetLayoutObject())
return nullptr;
InlineBox* inline_box = nullptr;
if (GetLayoutObject()->IsLayoutInline())
inline_box = ToLayoutInline(GetLayoutObject())->FirstLineBox();
else if (GetLayoutObject()->IsText())
inline_box = ToLayoutText(GetLayoutObject())->FirstTextBox();
if (!inline_box)
return nullptr;
AXObjectImpl* result = nullptr;
for (InlineBox* prev = inline_box->PrevOnLine(); prev;
prev = prev->PrevOnLine()) {
LayoutObject* layout_object =
LineLayoutAPIShim::LayoutObjectFrom(prev->GetLineLayoutItem());
result = AxObjectCache().GetOrCreate(layout_object);
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() == kStaticTextRole &&
result->Children().size())
result = result->Children()[result->Children().size() - 1].Get();
return result;
}
//
// Properties of interactive elements.
//
String AXLayoutObject::StringValue() const {
if (!layout_object_)
return String();
LayoutBoxModelObject* css_box = GetLayoutBoxModelObject();
if (css_box && css_box->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* select_element =
toHTMLSelectElement(layout_object_->GetNode());
int selected_index = select_element->selectedIndex();
const HeapVector<Member<HTMLElement>>& list_items =
select_element->GetListItems();
if (selected_index >= 0 &&
static_cast<size_t>(selected_index) < list_items.size()) {
const AtomicString& overridden_description =
list_items[selected_index]->FastGetAttribute(aria_labelAttr);
if (!overridden_description.IsNull())
return overridden_description;
}
return ToLayoutMenuList(layout_object_)->GetText();
}
if (IsWebArea()) {
// FIXME: Why would a layoutObject exist when the Document isn't attached to
// a frame?
if (layout_object_->GetFrame())
return String();
NOTREACHED();
}
if (IsTextControl())
return GetText();
if (layout_object_->IsFileUploadControl())
return ToLayoutFileUploadControl(layout_object_)->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 in_aria_labelled_by_traversal,
AXObjectSet& visited,
AXNameFrom& name_from,
AXRelatedObjectVector* related_objects,
NameSources* name_sources) const {
if (layout_object_) {
String text_alternative;
bool found_text_alternative = false;
if (layout_object_->IsBR()) {
text_alternative = String("\n");
found_text_alternative = true;
} else if (layout_object_->IsText() &&
(!recursive || !layout_object_->IsCounter())) {
LayoutText* layout_text = ToLayoutText(layout_object_);
String result = layout_text->PlainText();
if (!result.IsEmpty() || layout_text->IsAllCollapsibleWhitespace())
text_alternative = result;
else
text_alternative = layout_text->GetText();
found_text_alternative = true;
} else if (layout_object_->IsListMarker() && !recursive) {
text_alternative = ToLayoutListMarker(layout_object_)->GetText();
found_text_alternative = true;
}
if (found_text_alternative) {
name_from = kAXNameFromContents;
if (name_sources) {
name_sources->push_back(NameSource(false));
name_sources->back().type = name_from;
name_sources->back().text = text_alternative;
}
return text_alternative;
}
}
return AXNodeObject::TextAlternative(recursive, in_aria_labelled_by_traversal,
visited, name_from, related_objects,
name_sources);
}
//
// 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 (aria_role_) {
case kButtonRole:
case kSliderRole:
case kImageRole:
case kProgressIndicatorRole:
case kSpinButtonRole:
// case SeparatorRole:
return true;
default:
return false;
}
}
AXObjectImpl* AXLayoutObject::AncestorForWhichThisIsAPresentationalChild()
const {
// Walk the parent chain looking for a parent that has presentational children
AXObjectImpl* 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 EqualIgnoringASCIICase(grabbed, "true") ||
EqualIgnoringASCIICase(grabbed, "false");
}
bool AXLayoutObject::SupportsARIADropping() const {
const AtomicString& drop_effect = GetAttribute(aria_dropeffectAttr);
return !drop_effect.IsEmpty();
}
bool AXLayoutObject::SupportsARIAFlowTo() const {
return !GetAttribute(aria_flowtoAttr).IsEmpty();
}
bool AXLayoutObject::SupportsARIAOwns() const {
if (!layout_object_)
return false;
const AtomicString& aria_owns = GetAttribute(aria_ownsAttr);
return !aria_owns.IsEmpty();
}
//
// ARIA live-region features.
//
const AtomicString& AXLayoutObject::LiveRegionStatus() const {
DEFINE_STATIC_LOCAL(const AtomicString, live_region_status_assertive,
("assertive"));
DEFINE_STATIC_LOCAL(const AtomicString, live_region_status_polite,
("polite"));
DEFINE_STATIC_LOCAL(const AtomicString, live_region_status_off, ("off"));
const AtomicString& live_region_status =
GetAOMPropertyOrARIAAttribute(AOMStringProperty::kLive);
// These roles have implicit live region status.
if (live_region_status.IsEmpty()) {
switch (RoleValue()) {
case kAlertDialogRole:
case kAlertRole:
return live_region_status_assertive;
case kLogRole:
case kStatusRole:
return live_region_status_polite;
case kTimerRole:
case kMarqueeRole:
return live_region_status_off;
default:
break;
}
}
return live_region_status;
}
const AtomicString& AXLayoutObject::LiveRegionRelevant() const {
DEFINE_STATIC_LOCAL(const AtomicString, default_live_region_relevant,
("additions text"));
const AtomicString& relevant =
GetAOMPropertyOrARIAAttribute(AOMStringProperty::kRelevant);
// Default aria-relevant = "additions text".
if (relevant.IsEmpty())
return default_live_region_relevant;
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() == kAlertRole || RoleValue() == kStatusRole)) {
return true;
}
return ElementAttributeValue(aria_atomicAttr);
}
bool AXLayoutObject::LiveRegionBusy() const {
return ElementAttributeValue(aria_busyAttr);
}
//
// Hit testing.
//
AXObjectImpl* AXLayoutObject::AccessibilityHitTest(
const IntPoint& point) const {
if (!layout_object_ || !layout_object_->HasLayer())
return nullptr;
PaintLayer* layer = ToLayoutBox(layout_object_)->Layer();
HitTestRequest request(HitTestRequest::kReadOnly | HitTestRequest::kActive);
HitTestResult hit_test_result = HitTestResult(request, point);
layer->HitTest(hit_test_result);
Node* node = hit_test_result.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->GetLayoutObject();
if (!obj)
return nullptr;
AXObjectImpl* 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()) {
AXObjectImpl* control_object =
ToAXLayoutObject(result)->CorrespondingControlForLabelElement();
if (control_object && control_object->NameFromLabelElement())
return control_object;
}
result = result->ParentObjectUnignored();
}
return result;
}
AXObjectImpl* AXLayoutObject::ElementAccessibilityHitTest(
const IntPoint& point) const {
if (IsSVGImage())
return RemoteSVGElementHitTest(point);
return AXObjectImpl::ElementAccessibilityHitTest(point);
}
//
// High-level accessibility tree access.
//
AXObjectImpl* AXLayoutObject::ComputeParent() const {
DCHECK(!IsDetached());
if (!layout_object_)
return 0;
if (AriaRoleAttribute() == kMenuBarRole)
return AxObjectCache().GetOrCreate(layout_object_->Parent());
// menuButton and its corresponding menu are DOM siblings, but Accessibility
// needs them to be parent/child.
if (AriaRoleAttribute() == kMenuRole) {
AXObjectImpl* parent = MenuButtonForMenu();
if (parent)
return parent;
}
LayoutObject* parent_obj = LayoutParentObject();
if (parent_obj)
return AxObjectCache().GetOrCreate(parent_obj);
// A WebArea's parent should be the page popup owner, if any, otherwise null.
if (IsWebArea()) {
LocalFrame* frame = layout_object_->GetFrame();
return AxObjectCache().GetOrCreate(frame->PagePopupOwner());
}
return 0;
}
AXObjectImpl* AXLayoutObject::ComputeParentIfExists() const {
if (!layout_object_)
return 0;
if (AriaRoleAttribute() == kMenuBarRole)
return AxObjectCache().Get(layout_object_->Parent());
// menuButton and its corresponding menu are DOM siblings, but Accessibility
// needs them to be parent/child.
if (AriaRoleAttribute() == kMenuRole) {
AXObjectImpl* parent = MenuButtonForMenu();
if (parent)
return parent;
}
LayoutObject* parent_obj = LayoutParentObject();
if (parent_obj)
return AxObjectCache().Get(parent_obj);
// A WebArea's parent should be the page popup owner, if any, otherwise null.
if (IsWebArea()) {
LocalFrame* frame = layout_object_->GetFrame();
return AxObjectCache().Get(frame->PagePopupOwner());
}
return 0;
}
//
// Low-level accessibility tree exploration, only for use within the
// accessibility module.
//
AXObjectImpl* AXLayoutObject::RawFirstChild() const {
if (!layout_object_)
return 0;
LayoutObject* first_child = FirstChildConsideringContinuation(layout_object_);
if (!first_child)
return 0;
return AxObjectCache().GetOrCreate(first_child);
}
AXObjectImpl* AXLayoutObject::RawNextSibling() const {
if (!layout_object_)
return 0;
LayoutObject* next_sibling = 0;
LayoutInline* inline_continuation =
layout_object_->IsLayoutBlockFlow()
? ToLayoutBlockFlow(layout_object_)->InlineElementContinuation()
: nullptr;
if (inline_continuation) {
// Case 1: node is a block and has an inline continuation. Next sibling is
// the inline continuation's first child.
next_sibling = FirstChildConsideringContinuation(inline_continuation);
} else if (layout_object_->IsAnonymousBlock() &&
LastChildHasContinuation(layout_object_)) {
// 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* last_parent =
EndOfContinuations(ToLayoutBlock(layout_object_)->LastChild())
->Parent();
while (LastChildHasContinuation(last_parent))
last_parent = EndOfContinuations(last_parent->SlowLastChild())->Parent();
next_sibling = last_parent->NextSibling();
} else if (LayoutObject* ns = layout_object_->NextSibling()) {
// Case 3: node has an actual next sibling
next_sibling = ns;
} else if (IsInlineWithContinuation(layout_object_)) {
// Case 4: node is an inline with a continuation. Next sibling is the next
// sibling of the end of the continuation chain.
next_sibling = EndOfContinuations(layout_object_)->NextSibling();
} else if (layout_object_->Parent() &&
IsInlineWithContinuation(layout_object_->Parent())) {
// Case 5: node has no next sibling, and its parent is an inline with a
// continuation.
LayoutObject* continuation =
ToLayoutInline(layout_object_->Parent())->Continuation();
if (continuation->IsLayoutBlock()) {
// Case 5a: continuation is a block - in this case the block itself is the
// next sibling.
next_sibling = continuation;
} else {
// Case 5b: continuation is an inline - in this case the inline's first
// child is the next sibling.
next_sibling = FirstChildConsideringContinuation(continuation);
}
}
if (!next_sibling)
return 0;
return AxObjectCache().GetOrCreate(next_sibling);
}
void AXLayoutObject::AddChildren() {
DCHECK(!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.
DCHECK(!have_children_);
have_children_ = true;
if (!CanHaveChildren())
return;
HeapVector<Member<AXObjectImpl>> owned_children;
ComputeAriaOwnsChildren(owned_children);
for (AXObjectImpl* 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 : children_) {
if (!child->CachedParentObject())
child->SetParent(this);
}
for (const auto& owned_child : owned_children)
AddChild(owned_child);
}
bool AXLayoutObject::CanHaveChildren() const {
if (!layout_object_)
return false;
return AXNodeObject::CanHaveChildren();
}
void AXLayoutObject::UpdateChildrenIfNecessary() {
if (NeedsToUpdateChildren())
ClearChildren();
AXObjectImpl::UpdateChildrenIfNecessary();
}
void AXLayoutObject::ClearChildren() {
AXObjectImpl::ClearChildren();
children_dirty_ = false;
}
//
// Properties of the object's owning document or page.
//
double AXLayoutObject::EstimatedLoadingProgress() const {
if (!layout_object_)
return 0;
if (IsLoaded())
return 1.0;
if (LocalFrame* frame = layout_object_->GetDocument().GetFrame())
return frame->Loader().Progress().EstimatedProgress();
return 0;
}
//
// DOM and layout tree access.
//
Node* AXLayoutObject::GetNode() const {
return GetLayoutObject() ? GetLayoutObject()->GetNode() : nullptr;
}
Document* AXLayoutObject::GetDocument() const {
if (!GetLayoutObject())
return nullptr;
return &GetLayoutObject()->GetDocument();
}
FrameView* AXLayoutObject::DocumentFrameView() const {
if (!GetLayoutObject())
return nullptr;
// this is the LayoutObject's Document's LocalFrame's FrameView
return GetLayoutObject()->GetDocument().View();
}
Element* AXLayoutObject::AnchorElement() const {
if (!layout_object_)
return 0;
AXObjectCacheImpl& cache = AxObjectCache();
LayoutObject* curr_layout_object;
// Search up the layout tree for a LayoutObject with a DOM node. Defer to an
// earlier continuation, though.
for (curr_layout_object = layout_object_;
curr_layout_object && !curr_layout_object->GetNode();
curr_layout_object = curr_layout_object->Parent()) {
if (curr_layout_object->IsAnonymousBlock() &&
curr_layout_object->IsLayoutBlockFlow()) {
LayoutObject* continuation =
ToLayoutBlockFlow(curr_layout_object)->Continuation();
if (continuation)
return cache.GetOrCreate(continuation)->AnchorElement();
}
}
// bail if none found
if (!curr_layout_object)
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 = curr_layout_object->GetNode();
if (!node)
return nullptr;
for (Node& runner : NodeTraversal::InclusiveAncestorsOf(*node)) {
if (isHTMLAnchorElement(runner) ||
(runner.GetLayoutObject() &&
cache.GetOrCreate(runner.GetLayoutObject())->IsAnchor()))
return ToElement(&runner);
}
return 0;
}
//
// Functions that retrieve the current selection.
//
AXObjectImpl::AXRange AXLayoutObject::Selection() const {
AXRange text_selection = TextControlSelection();
if (text_selection.IsValid())
return text_selection;
if (!GetLayoutObject() || !GetLayoutObject()->GetFrame())
return AXRange();
VisibleSelection selection =
GetLayoutObject()
->GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated();
if (selection.IsNone())
return AXRange();
VisiblePosition visible_start = selection.VisibleStart();
Position start = visible_start.ToParentAnchoredPosition();
TextAffinity start_affinity = visible_start.Affinity();
VisiblePosition visible_end = selection.VisibleEnd();
Position end = visible_end.ToParentAnchoredPosition();
TextAffinity end_affinity = visible_end.Affinity();
Node* anchor_node = start.AnchorNode();
DCHECK(anchor_node);
AXLayoutObject* anchor_object = nullptr;
// Find the closest node that has a corresponding AXObjectImpl.
// 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 (anchor_node) {
anchor_object = GetUnignoredObjectFromNode(*anchor_node);
if (anchor_object)
break;
if (anchor_node->nextSibling())
anchor_node = anchor_node->nextSibling();
else
anchor_node = anchor_node->parentNode();
}
Node* focus_node = end.AnchorNode();
DCHECK(focus_node);
AXLayoutObject* focus_object = nullptr;
while (focus_node) {
focus_object = GetUnignoredObjectFromNode(*focus_node);
if (focus_object)
break;
if (focus_node->previousSibling())
focus_node = focus_node->previousSibling();
else
focus_node = focus_node->parentNode();
}
if (!anchor_object || !focus_object)
return AXRange();
int anchor_offset = anchor_object->IndexForVisiblePosition(visible_start);
DCHECK_GE(anchor_offset, 0);
int focus_offset = focus_object->IndexForVisiblePosition(visible_end);
DCHECK_GE(focus_offset, 0);
return AXRange(anchor_object, anchor_offset, start_affinity, focus_object,
focus_offset, end_affinity);
}
// 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.
AXObjectImpl::AXRange AXLayoutObject::SelectionUnderObject() const {
AXRange text_selection = TextControlSelection();
if (text_selection.IsValid())
return text_selection;
if (!GetNode() || !GetLayoutObject()->GetFrame())
return AXRange();
VisibleSelection selection =
GetLayoutObject()
->GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated();
Range* selection_range = CreateRange(FirstEphemeralRangeOf(selection));
ContainerNode* parent_node = GetNode()->parentNode();
int node_index = GetNode()->NodeIndex();
if (!selection_range
// Selection is contained in node.
|| !(parent_node &&
selection_range->comparePoint(parent_node, node_index,
IGNORE_EXCEPTION_FOR_TESTING) < 0 &&
selection_range->comparePoint(parent_node, node_index + 1,
IGNORE_EXCEPTION_FOR_TESTING) > 0)) {
return AXRange();
}
int start = IndexForVisiblePosition(selection.VisibleStart());
DCHECK_GE(start, 0);
int end = IndexForVisiblePosition(selection.VisibleEnd());
DCHECK_GE(end, 0);
return AXRange(start, end);
}
AXObjectImpl::AXRange AXLayoutObject::TextControlSelection() const {
if (!GetLayoutObject())
return AXRange();
LayoutObject* layout = nullptr;
if (GetLayoutObject()->IsTextControl()) {
layout = GetLayoutObject();
} else {
Element* focused_element = GetDocument()->FocusedElement();
if (focused_element && focused_element->GetLayoutObject() &&
focused_element->GetLayoutObject()->IsTextControl())
layout = focused_element->GetLayoutObject();
}
if (!layout)
return AXRange();
AXObjectImpl* ax_object = AxObjectCache().GetOrCreate(layout);
if (!ax_object || !ax_object->IsAXLayoutObject())
return AXRange();
VisibleSelection selection =
layout->GetFrame()
->Selection()
.ComputeVisibleSelectionInDOMTreeDeprecated();
TextControlElement* text_control =
ToLayoutTextControl(layout)->GetTextControlElement();
DCHECK(text_control);
int start = text_control->selectionStart();
int end = text_control->selectionEnd();
return AXRange(ax_object, start, selection.VisibleStart().Affinity(),
ax_object, end, selection.VisibleEnd().Affinity());
}
int AXLayoutObject::IndexForVisiblePosition(
const VisiblePosition& position) const {
if (GetLayoutObject() && GetLayoutObject()->IsTextControl()) {
TextControlElement* text_control =
ToLayoutTextControl(GetLayoutObject())->GetTextControlElement();
return text_control->IndexForVisiblePosition(position);
}
if (!GetNode())
return 0;
Position index_position = position.DeepEquivalent();
if (index_position.IsNull())
return 0;
Range* range = Range::Create(*GetDocument());
range->setStart(GetNode(), 0, IGNORE_EXCEPTION_FOR_TESTING);
range->setEnd(index_position, IGNORE_EXCEPTION_FOR_TESTING);
return TextIterator::RangeLength(range->StartPosition(),
range->EndPosition());
}
AXLayoutObject* AXLayoutObject::GetUnignoredObjectFromNode(Node& node) const {
if (IsDetached())
return nullptr;
AXObjectImpl* ax_object = AxObjectCache().GetOrCreate(&node);
if (!ax_object)
return nullptr;
if (ax_object->IsAXLayoutObject() && !ax_object->AccessibilityIsIgnored())
return ToAXLayoutObject(ax_object);
return nullptr;
}
//
// Modify or take an action on an object.
//
// Convert from an accessible object and offset to a VisiblePosition.
static VisiblePosition ToVisiblePosition(AXObjectImpl* obj, int offset) {
if (!obj->GetNode())
return VisiblePosition();
Node* node = obj->GetNode();
if (!node->IsTextNode()) {
int child_count = obj->Children().size();
// Place position immediately before the container node, if there was no
// children.
if (child_count == 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 > child_count)
return VisiblePosition();
// Clamp to between 0 and child count - 1.
int clamped_offset =
static_cast<unsigned>(offset) > (obj->Children().size() - 1)
? offset - 1
: offset;
AXObjectImpl* child_obj = obj->Children()[clamped_offset];
Node* child_node = child_obj->GetNode();
if (!child_node || !child_node->parentNode())
return VisiblePosition();
// The index in parent.
int adjusted_offset = child_node->NodeIndex();
// If we had to clamp the offset above, the client wants to select the
// end of the node.
if (clamped_offset != offset)
adjusted_offset++;
return CreateVisiblePosition(
Position::EditingPositionOf(child_node->parentNode(), adjusted_offset));
}
// 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 node_position = blink::VisiblePositionBeforeNode(*node);
int node_index = blink::IndexForVisiblePosition(node_position, parent);
return blink::VisiblePositionForIndex(node_index + offset, parent);
}
void AXLayoutObject::SetSelection(const AXRange& selection) {
if (!GetLayoutObject() || !selection.IsValid())
return;
AXObjectImpl* anchor_object =
selection.anchor_object ? selection.anchor_object.Get() : this;
AXObjectImpl* focus_object =
selection.focus_object ? selection.focus_object.Get() : this;
if (!IsValidSelectionBound(anchor_object) ||
!IsValidSelectionBound(focus_object)) {
return;
}
// The selection offsets are offsets into the accessible value.
if (anchor_object == focus_object &&
anchor_object->GetLayoutObject()->IsTextControl()) {
TextControlElement* text_control =
ToLayoutTextControl(anchor_object->GetLayoutObject())
->GetTextControlElement();
if (selection.anchor_offset <= selection.focus_offset) {
text_control->SetSelectionRange(selection.anchor_offset,
selection.focus_offset,
kSelectionHasForwardDirection);
} else {
text_control->SetSelectionRange(selection.focus_offset,
selection.anchor_offset,
kSelectionHasBackwardDirection);
}
return;
}
LocalFrame* frame = GetLayoutObject()->GetFrame();
if (!frame)
return;
// TODO(editing-dev): Use of updateStyleAndLayoutIgnorePendingStylesheets
// needs to be audited. see http://crbug.com/590369 for more details.
// This callsite should probably move up the stack.
frame->GetDocument()->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 anchor_visible_position =
ToVisiblePosition(anchor_object, selection.anchor_offset);
VisiblePosition focus_visible_position =
ToVisiblePosition(focus_object, selection.focus_offset);
if (anchor_visible_position.IsNull() || focus_visible_position.IsNull())
return;
frame->Selection().SetSelection(
SelectionInDOMTree::Builder()
.Collapse(anchor_visible_position.ToPositionWithAffinity())
.Extend(focus_visible_position.DeepEquivalent())
.Build());
}
bool AXLayoutObject::IsValidSelectionBound(
const AXObjectImpl* bound_object) const {
return GetLayoutObject() && bound_object && !bound_object->IsDetached() &&
bound_object->IsAXLayoutObject() && bound_object->GetLayoutObject() &&
bound_object->GetLayoutObject()->GetFrame() ==
GetLayoutObject()->GetFrame() &&
&bound_object->AxObjectCache() == &AxObjectCache();
}
void AXLayoutObject::SetValue(const String& string) {
if (!GetNode() || !GetNode()->IsElementNode())
return;
if (!layout_object_ || !layout_object_->IsBoxModelObject())
return;
LayoutBoxModelObject* layout_object = ToLayoutBoxModelObject(layout_object_);
if (layout_object->IsTextField() && isHTMLInputElement(*GetNode()))
toHTMLInputElement(*GetNode())
.setValue(string, kDispatchInputAndChangeEvent);
else if (layout_object->IsTextArea() && isHTMLTextAreaElement(*GetNode()))
toHTMLTextAreaElement(*GetNode())
.setValue(string, kDispatchInputAndChangeEvent);
}
//
// Notifications that this object may have changed.
//
void AXLayoutObject::HandleActiveDescendantChanged() {
if (!GetLayoutObject())
return;
AXObjectImpl* focused_object = AxObjectCache().FocusedObject();
if (focused_object == this && SupportsActiveDescendant()) {
AxObjectCache().PostNotification(
GetLayoutObject(), AXObjectCacheImpl::kAXActiveDescendantChanged);
}
}
void AXLayoutObject::HandleAriaExpandedChanged() {
// Find if a parent of this object should handle aria-expanded changes.
AXObjectImpl* container_parent = this->ParentObject();
while (container_parent) {
bool found_parent = false;
switch (container_parent->RoleValue()) {
case kTreeRole:
case kTreeGridRole:
case kGridRole:
case kTableRole:
found_parent = true;
break;
default:
break;
}
if (found_parent)
break;
container_parent = container_parent->ParentObject();
}
// Post that the row count changed.
if (container_parent)
AxObjectCache().PostNotification(container_parent,
AXObjectCacheImpl::kAXRowCountChanged);
// Post that the specific row either collapsed or expanded.
AccessibilityExpanded expanded = IsExpanded();
if (!expanded)
return;
if (RoleValue() == kRowRole || RoleValue() == kTreeItemRole) {
AXObjectCacheImpl::AXNotification notification =
AXObjectCacheImpl::kAXRowExpanded;
if (expanded == kExpandedCollapsed)
notification = AXObjectCacheImpl::kAXRowCollapsed;
AxObjectCache().PostNotification(this, notification);
} else {
AxObjectCache().PostNotification(this,
AXObjectCacheImpl::kAXExpandedChanged);
}
}
void AXLayoutObject::TextChanged() {
if (!layout_object_)
return;
Settings* settings = GetDocument()->GetSettings();
if (settings && settings->GetInlineTextBoxAccessibilityEnabled() &&
RoleValue() == kStaticTextRole)
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(layout_object_, position.DeepEquivalent()))
return IndexForVisiblePosition(position);
return -1;
}
VisiblePosition AXLayoutObject::VisiblePositionForIndex(int index) const {
if (!layout_object_)
return VisiblePosition();
if (layout_object_->IsTextControl())
return ToLayoutTextControl(layout_object_)
->GetTextControlElement()
->VisiblePositionForIndex(index);
Node* node = layout_object_->GetNode();
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::kUpstream);
}
void AXLayoutObject::AddInlineTextBoxChildren(bool force) {
Settings* settings = GetDocument()->GetSettings();
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* layout_text = ToLayoutText(GetLayoutObject());
for (RefPtr<AbstractInlineTextBox> box =
layout_text->FirstAbstractInlineTextBox();
box.Get(); box = box->NextInlineTextBox()) {
AXObjectImpl* ax_object = AxObjectCache().GetOrCreate(box.Get());
if (!ax_object->AccessibilityIsIgnored())
children_.push_back(ax_object);
}
}
void AXLayoutObject::LineBreaks(Vector<int>& line_breaks) const {
if (!IsTextControl())
return;
VisiblePosition visible_pos = VisiblePositionForIndex(0);
VisiblePosition prev_visible_pos = visible_pos;
visible_pos = NextLinePosition(visible_pos, LayoutUnit(), kHasEditableAXRole);
// nextLinePosition moves to the end of the current line when there are
// no more lines.
while (visible_pos.IsNotNull() &&
!InSameLine(prev_visible_pos, visible_pos)) {
line_breaks.push_back(IndexForVisiblePosition(visible_pos));
prev_visible_pos = visible_pos;
visible_pos =
NextLinePosition(visible_pos, LayoutUnit(), kHasEditableAXRole);
// Make sure we always make forward progress.
if (visible_pos.DeepEquivalent().CompareTo(
prev_visible_pos.DeepEquivalent()) < 0)
break;
}
}
//
// Private.
//
AXObjectImpl* AXLayoutObject::TreeAncestorDisallowingChild() const {
// Determine if this is in a tree. If so, we apply special behavior to make it
// work like an AXOutline.
AXObjectImpl* ax_obj = ParentObject();
AXObjectImpl* tree_ancestor = 0;
while (ax_obj) {
if (ax_obj->IsTree()) {
tree_ancestor = ax_obj;
break;
}
ax_obj = ax_obj->ParentObject();
}
// If the object is in a tree, only tree items should be exposed (and the
// children of tree items).
if (tree_ancestor) {
AccessibilityRole role = RoleValue();
if (role != kTreeItemRole && role != kStaticTextRole)
return tree_ancestor;
}
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.
AXObjectImpl* focused_element = AxObjectCache().FocusedObject();
if (!focused_element)
return false;
HeapVector<Member<Element>> elements;
ElementsFromAttribute(elements, aria_controlsAttr);
for (const auto& element : elements) {
AXObjectImpl* tab_panel = AxObjectCache().GetOrCreate(element);
// A tab item should only control tab panels.
if (!tab_panel || tab_panel->RoleValue() != kTabPanelRole)
continue;
AXObjectImpl* check_focus_element = focused_element;
// Check if the focused element is a descendant of the element controlled by
// the tab item.
while (check_focus_element) {
if (tab_panel == check_focus_element)
return true;
check_focus_element = check_focus_element->ParentObject();
}
}
return false;
}
AXObjectImpl* AXLayoutObject::AccessibilityImageMapHitTest(
HTMLAreaElement* area,
const IntPoint& point) const {
if (!area)
return 0;
AXObjectImpl* 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 (!layout_object_)
return 0;
LayoutObject* start_of_conts = layout_object_->IsLayoutBlockFlow()
? StartOfContinuations(layout_object_)
: nullptr;
if (start_of_conts) {
// Case 1: node is a block and is an inline's continuation. Parent
// is the start of the continuation chain.
return start_of_conts;
}
LayoutObject* parent = layout_object_->Parent();
start_of_conts =
parent && parent->IsLayoutInline() ? StartOfContinuations(parent) : 0;
if (start_of_conts) {
// Case 2: node's parent is an inline which is some node's continuation;
// parent is the earliest node in the continuation chain.
return start_of_conts;
}
LayoutObject* first_child = parent ? parent->SlowFirstChild() : 0;
if (first_child && first_child->GetNode()) {
// 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* node_layout_first_child =
first_child->GetNode()->GetLayoutObject();
node_layout_first_child != first_child;
node_layout_first_child = first_child->GetNode()->GetLayoutObject()) {
for (LayoutObject* conts_test = node_layout_first_child; conts_test;
conts_test = NextContinuation(conts_test)) {
if (conts_test == first_child) {
parent = node_layout_first_child->Parent();
break;
}
}
LayoutObject* new_first_child = parent->SlowFirstChild();
if (first_child == new_first_child)
break;
first_child = new_first_child;
if (!first_child->GetNode())
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;
}
AXObjectImpl* AXLayoutObject::RemoteSVGElementHitTest(
const IntPoint& point) const {
AXObjectImpl* 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 (AXObjectImpl* 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 should_insert_hidden_nodes = false;
for (Node& child : NodeTraversal::ChildrenOf(*node)) {
if (!child.GetLayoutObject() && IsNodeAriaVisible(&child)) {
should_insert_hidden_nodes = true;
break;
}
}
if (!should_insert_hidden_nodes)
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 insertion_index = 0;
for (Node& child : NodeTraversal::ChildrenOf(*node)) {
if (child.GetLayoutObject()) {
// Find out where the last layout sibling is located within m_children.
if (AXObjectImpl* child_object =
AxObjectCache().Get(child.GetLayoutObject())) {
if (child_object->AccessibilityIsIgnored()) {
const auto& children = child_object->Children();
child_object = children.size() ? children.back().Get() : 0;
}
if (child_object)
insertion_index = children_.Find(child_object) + 1;
continue;
}
}
if (!IsNodeAriaVisible(&child))
continue;
unsigned previous_size = children_.size();
if (insertion_index > previous_size)
insertion_index = previous_size;
InsertChild(AxObjectCache().GetOrCreate(&child), insertion_index);
insertion_index += (children_.size() - previous_size);
}
}
void AXLayoutObject::AddTextFieldChildren() {
Node* node = this->GetNode();
if (!isHTMLInputElement(node))
return;
HTMLInputElement& input = toHTMLInputElement(*node);
Element* spin_button_element = input.UserAgentShadowRoot()->getElementById(
ShadowElementNames::SpinButton());
if (!spin_button_element || !spin_button_element->IsSpinButtonElement())
return;
AXSpinButton* ax_spin_button =
ToAXSpinButton(AxObjectCache().GetOrCreate(kSpinButtonRole));
ax_spin_button->SetSpinButtonElement(
ToSpinButtonElement(spin_button_element));
ax_spin_button->SetParent(this);
children_.push_back(ax_spin_button);
}
void AXLayoutObject::AddImageMapChildren() {
LayoutBoxModelObject* css_box = GetLayoutBoxModelObject();
if (!css_box || !css_box->IsLayoutImage())
return;
HTMLMapElement* map = ToLayoutImage(css_box)->ImageMap();
if (!map)
return;
for (HTMLAreaElement& area :
Traversal<HTMLAreaElement>::DescendantsOf(*map)) {
// add an <area> element for this child if it has a link
AXObjectImpl* obj = AxObjectCache().GetOrCreate(&area);
if (obj) {
AXImageMapLink* area_object = ToAXImageMapLink(obj);
area_object->SetParent(this);
DCHECK(area_object->AxObjectID() != 0);
if (!area_object->AccessibilityIsIgnored())
children_.push_back(area_object);
else
AxObjectCache().Remove(area_object->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.
DCHECK(!children_.size());
have_children_ = false;
AXNodeObject::AddChildren();
}
void AXLayoutObject::AddPopupChildren() {
if (!isHTMLInputElement(GetNode()))
return;
if (AXObjectImpl* ax_popup =
ToAXObjectImpl(toHTMLInputElement(GetNode())->PopupRootAXObject()))
children_.push_back(ax_popup);
}
void AXLayoutObject::AddRemoteSVGChildren() {
AXSVGRoot* root = RemoteSVGRootElement();
if (!root)
return;
root->SetParent(this);
if (root->AccessibilityIsIgnored()) {
for (const auto& child : root->Children())
children_.push_back(child);
} else {
children_.push_back(root);
}
}
bool AXLayoutObject::ElementAttributeValue(
const QualifiedName& attribute_name) const {
if (!layout_object_)
return false;
return EqualIgnoringASCIICase(GetAttribute(attribute_name), "true");
}
} // namespace blink