summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc')
-rw-r--r--chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc125
1 files changed, 91 insertions, 34 deletions
diff --git a/chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc b/chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc
index 1bc953837a6..6be853a7529 100644
--- a/chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -28,6 +28,7 @@
#include "third_party/blink/renderer/modules/accessibility/ax_object.h"
+#include "third_party/blink/public/common/features.h"
#include "third_party/blink/public/platform/web_scroll_into_view_params.h"
#include "third_party/blink/renderer/core/aom/accessible_node.h"
#include "third_party/blink/renderer/core/aom/accessible_node_list.h"
@@ -55,7 +56,9 @@
#include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
#include "third_party/blink/renderer/modules/accessibility/ax_range.h"
#include "third_party/blink/renderer/modules/accessibility/ax_sparse_attribute_setter.h"
+#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/language.h"
+#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/text/platform_locale.h"
#include "third_party/blink/renderer/platform/wtf/hash_set.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
@@ -63,12 +66,8 @@
#include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
#include "third_party/skia/include/core/SkMatrix44.h"
-using blink::WebLocalizedString;
-
namespace blink {
-using namespace html_names;
-
namespace {
struct RoleHashTraits : HashTraits<ax::mojom::Role> {
@@ -91,6 +90,11 @@ struct RoleEntry {
const RoleEntry kRoles[] = {
{"alert", ax::mojom::Role::kAlert},
{"alertdialog", ax::mojom::Role::kAlertDialog},
+ {"annotation-attribution", ax::mojom::Role::kAnnotationAttribution},
+ {"annotation-commentary", ax::mojom::Role::kAnnotationCommentary},
+ {"annotation-presence", ax::mojom::Role::kAnnotationPresence},
+ {"annotation-revision", ax::mojom::Role::kAnnotationRevision},
+ {"annotation-suggestion", ax::mojom::Role::kAnnotationSuggestion},
{"application", ax::mojom::Role::kApplication},
{"article", ax::mojom::Role::kArticle},
{"banner", ax::mojom::Role::kBanner},
@@ -230,7 +234,11 @@ const InternalRoleEntry kInternalRoles[] = {
{ax::mojom::Role::kAlertDialog, "AlertDialog"},
{ax::mojom::Role::kAlert, "Alert"},
{ax::mojom::Role::kAnchor, "Anchor"},
- {ax::mojom::Role::kAnnotation, "Annotation"},
+ {ax::mojom::Role::kAnnotationAttribution, "kAnnotationAttribution"},
+ {ax::mojom::Role::kAnnotationCommentary, "AnnotationCommentary"},
+ {ax::mojom::Role::kAnnotationPresence, "AnnotationPresence"},
+ {ax::mojom::Role::kAnnotationRevision, "AnnotationRevision"},
+ {ax::mojom::Role::kAnnotationSuggestion, "AnnotationSuggestion"},
{ax::mojom::Role::kApplication, "Application"},
{ax::mojom::Role::kArticle, "Article"},
{ax::mojom::Role::kAudio, "Audio"},
@@ -313,6 +321,7 @@ const InternalRoleEntry kInternalRoles[] = {
{ax::mojom::Role::kFigcaption, "Figcaption"},
{ax::mojom::Role::kFigure, "Figure"},
{ax::mojom::Role::kFooter, "Footer"},
+ {ax::mojom::Role::kFooterAsNonLandmark, "FooterAsNonLandmark"},
{ax::mojom::Role::kForm, "Form"},
{ax::mojom::Role::kGenericContainer, "GenericContainer"},
// --------------------------------------------------------------
@@ -325,6 +334,8 @@ const InternalRoleEntry kInternalRoles[] = {
// --------------------------------------------------------------
{ax::mojom::Role::kGrid, "Grid"},
{ax::mojom::Role::kGroup, "Group"},
+ {ax::mojom::Role::kHeader, "Header"},
+ {ax::mojom::Role::kHeaderAsNonLandmark, "HeaderAsNonLandmark"},
{ax::mojom::Role::kHeading, "Heading"},
{ax::mojom::Role::kIframePresentational, "IframePresentational"},
{ax::mojom::Role::kIframe, "Iframe"},
@@ -377,6 +388,8 @@ const InternalRoleEntry kInternalRoles[] = {
{ax::mojom::Role::kRowHeader, "RowHeader"},
{ax::mojom::Role::kRow, "Row"},
{ax::mojom::Role::kRuby, "Ruby"},
+ {ax::mojom::Role::kRubyAnnotation, "RubyAnnotation"},
+ {ax::mojom::Role::kSection, "Section"},
{ax::mojom::Role::kSvgRoot, "SVGRoot"},
{ax::mojom::Role::kScrollBar, "ScrollBar"},
{ax::mojom::Role::kScrollView, "ScrollView"},
@@ -418,12 +431,14 @@ static_assert(base::size(kInternalRoles) ==
// Roles which we need to map in the other direction
const RoleEntry kReverseRoles[] = {
+ {"banner", ax::mojom::Role::kHeader},
{"button", ax::mojom::Role::kToggleButton},
{"combobox", ax::mojom::Role::kPopUpButton},
{"contentinfo", ax::mojom::Role::kFooter},
{"menuitem", ax::mojom::Role::kMenuButton},
{"menuitem", ax::mojom::Role::kMenuListOption},
{"progressbar", ax::mojom::Role::kMeter},
+ {"region", ax::mojom::Role::kSection},
{"textbox", ax::mojom::Role::kTextField},
{"combobox", ax::mojom::Role::kComboBoxMenuButton},
{"combobox", ax::mojom::Role::kTextFieldWithComboBox}};
@@ -808,10 +823,12 @@ bool AXObject::IsLandmarkRelated() const {
case ax::mojom::Role::kDocToc:
case ax::mojom::Role::kFooter:
case ax::mojom::Role::kForm:
+ case ax::mojom::Role::kHeader:
case ax::mojom::Role::kMain:
case ax::mojom::Role::kNavigation:
case ax::mojom::Role::kRegion:
case ax::mojom::Role::kSearch:
+ case ax::mojom::Role::kSection:
return true;
default:
return false;
@@ -1081,7 +1098,7 @@ const AXObject* AXObject::InertRoot() const {
element = FlatTreeTraversal::ParentElement(*node);
while (element) {
- if (element->hasAttribute(kInertAttr))
+ if (element->hasAttribute(html_names::kInertAttr))
return AXObjectCache().GetOrCreate(element);
element = FlatTreeTraversal::ParentElement(*element);
}
@@ -1195,11 +1212,6 @@ bool AXObject::ComputeAccessibilityIsIgnoredButIncludedInTree() const {
if (!GetNode())
return false;
- // Disallow inert nodes from the tree to ensure the dialog is always the
- // first child of the root.
- if (GetNode()->IsInert())
- return false;
-
// If the node is part of the user agent shadow dom, or has the explicit
// internal Role::kIgnored, they aren't interesting for paragraph navigation
// or LabelledBy/DescribedBy relationships.
@@ -1213,16 +1225,29 @@ bool AXObject::ComputeAccessibilityIsIgnoredButIncludedInTree() const {
if (IsLineBreakingObject())
return true;
- // Allow the browser side ax tree to access aria-hidden="true", "visibility:
- // hidden", and "visibility: collapse" nodes. This is useful for APIs that
- // return the node referenced by aria-labeledby and aria-describedby
- if (GetLayoutObject()) {
+ // Allow the browser side ax tree to access "visibility: [hidden|collapse]"
+ // and "display: none" nodes. This is useful for APIs that return the node
+ // referenced by aria-labeledby and aria-describedby.
+ // An element must have an id attribute or it cannot be referenced by
+ // aria-labelledby or aria-describedby.
+ if (RuntimeEnabledFeatures::AccessibilityExposeDisplayNoneEnabled()) {
+ if (Element* element = GetElement()) {
+ if (element->FastHasAttribute(html_names::kIdAttr) &&
+ IsHiddenForTextAlternativeCalculation()) {
+ return true;
+ }
+ }
+ } else if (GetLayoutObject()) {
if (GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible)
return true;
- if (AriaHiddenRoot())
- return true;
}
+ // Allow the browser side ax tree to access "aria-hidden" nodes.
+ // This is useful for APIs that return the node referenced by
+ // aria-labeledby and aria-describedby.
+ if (GetLayoutObject() && AriaHiddenRoot())
+ return true;
+
return false;
}
@@ -1291,7 +1316,7 @@ bool AXObject::CanReceiveAccessibilityFocus() const {
return true;
// aria-activedescendant focus
- return elem->FastHasAttribute(kIdAttr) && CanBeActiveDescendant();
+ return elem->FastHasAttribute(html_names::kIdAttr) && CanBeActiveDescendant();
}
bool AXObject::CanSetValueAttribute() const {
@@ -1536,6 +1561,9 @@ String AXObject::GetName(ax::mojom::NameFrom& name_from,
bool hidden_and_ignored_but_included_in_tree =
IsHiddenForTextAlternativeCalculation() &&
AccessibilityIsIgnoredButIncludedInTree();
+ // Initialize |name_from|, as TextAlternative() might never set it in some
+ // cases.
+ name_from = ax::mojom::NameFrom::kNone;
String text = TextAlternative(false, hidden_and_ignored_but_included_in_tree,
visited, name_from, &related_objects, nullptr);
@@ -1610,8 +1638,7 @@ bool AXObject::IsHiddenForTextAlternativeCalculation() const {
if (Node* node = GetNode()) {
auto* element = DynamicTo<Element>(node);
if (element && node->isConnected()) {
- scoped_refptr<ComputedStyle> style =
- document->EnsureStyleResolver().StyleForElement(element);
+ const ComputedStyle* style = element->EnsureComputedStyle();
return style->Display() == EDisplay::kNone ||
style->Visibility() != EVisibility::kVisible;
}
@@ -1645,9 +1672,10 @@ String AXObject::AriaTextAlternative(bool recursive,
// Check ARIA attributes.
const QualifiedName& attr =
- HasAttribute(kAriaLabeledbyAttr) && !HasAttribute(kAriaLabelledbyAttr)
- ? kAriaLabeledbyAttr
- : kAriaLabelledbyAttr;
+ HasAttribute(html_names::kAriaLabeledbyAttr) &&
+ !HasAttribute(html_names::kAriaLabelledbyAttr)
+ ? html_names::kAriaLabeledbyAttr
+ : html_names::kAriaLabelledbyAttr;
if (name_sources) {
name_sources->push_back(NameSource(*found_text_alternative, attr));
@@ -1690,7 +1718,7 @@ String AXObject::AriaTextAlternative(bool recursive,
name_from = ax::mojom::NameFrom::kAttribute;
if (name_sources) {
name_sources->push_back(
- NameSource(*found_text_alternative, kAriaLabelAttr));
+ NameSource(*found_text_alternative, html_names::kAriaLabelAttr));
name_sources->back().type = name_from;
}
const AtomicString& aria_label =
@@ -1777,9 +1805,9 @@ void AXObject::AriaLabelledbyElementVector(
HeapVector<Member<Element>>& elements,
Vector<String>& ids) const {
// Try both spellings, but prefer aria-labelledby, which is the official spec.
- ElementsFromAttribute(elements, kAriaLabelledbyAttr, ids);
+ ElementsFromAttribute(elements, html_names::kAriaLabelledbyAttr, ids);
if (!ids.size())
- ElementsFromAttribute(elements, kAriaLabeledbyAttr, ids);
+ ElementsFromAttribute(elements, html_names::kAriaLabeledbyAttr, ids);
}
String AXObject::TextFromAriaLabelledby(AXObjectSet& visited,
@@ -1794,7 +1822,7 @@ String AXObject::TextFromAriaDescribedby(AXRelatedObjectVector* related_objects,
Vector<String>& ids) const {
AXObjectSet visited;
HeapVector<Member<Element>> elements;
- ElementsFromAttribute(elements, kAriaDescribedbyAttr, ids);
+ ElementsFromAttribute(elements, html_names::kAriaDescribedbyAttr, ids);
return TextFromElements(true, visited, elements, related_objects);
}
@@ -2043,6 +2071,22 @@ ax::mojom::Role AXObject::DetermineAriaRoleAttribute() const {
ax::mojom::Role role = AriaRoleToWebCoreRole(aria_role);
+ switch (role) {
+ case ax::mojom::Role::kAnnotationAttribution:
+ case ax::mojom::Role::kAnnotationCommentary:
+ case ax::mojom::Role::kAnnotationPresence:
+ case ax::mojom::Role::kAnnotationRevision:
+ case ax::mojom::Role::kAnnotationSuggestion:
+ UseCounter::Count(GetDocument(), WebFeature::kARIAAnnotationRoles);
+ if (!RuntimeEnabledFeatures::
+ AccessibilityExposeARIAAnnotationsEnabled()) {
+ role = ax::mojom::Role::kGenericContainer;
+ }
+ break;
+ default:
+ break;
+ }
+
// ARIA states if an item can get focus, it should not be presentational.
// It also states user agents should ignore the presentational role if
// the element has global ARIA states and properties.
@@ -2516,8 +2560,10 @@ void AXObject::UpdateChildrenIfNecessary() {
void AXObject::ClearChildren() {
// Detach all weak pointers from objects to their parents.
- for (const auto& child : children_)
- child->DetachFromParent();
+ for (const auto& child : children_) {
+ if (child->parent_ == this)
+ child->DetachFromParent();
+ }
children_.clear();
have_children_ = false;
@@ -2568,7 +2614,7 @@ AtomicString AXObject::Language() const {
// 2. The list of languages the browser sends in the [Accept-Language] header.
// 3. The browser's default language.
- const AtomicString& lang = GetAttribute(kLangAttr);
+ const AtomicString& lang = GetAttribute(html_names::kLangAttr);
if (!lang.IsEmpty())
return lang;
@@ -3161,8 +3207,10 @@ bool AXObject::OnNativeScrollToMakeVisibleAction() const {
if (!node)
return false;
if (Element* locked_ancestor =
- DisplayLockUtilities::NearestLockedInclusiveAncestor(*node))
- locked_ancestor->ActivateDisplayLockIfNeeded();
+ DisplayLockUtilities::NearestLockedInclusiveAncestor(*node)) {
+ locked_ancestor->ActivateDisplayLockIfNeeded(
+ DisplayLockActivationReason::kUser);
+ }
LayoutObject* layout_object = node->GetLayoutObject();
if (!layout_object || !node->isConnected())
return false;
@@ -3314,7 +3362,7 @@ bool AXObject::NameFromSelectedOption(bool recursive) const {
// This can be either a button widget with a non-false value of
// aria-haspopup or a select element with size of 1.
case ax::mojom::Role::kPopUpButton:
- return ToHTMLSelectElementOrNull(*GetNode()) ? recursive : false;
+ return DynamicTo<HTMLSelectElement>(*GetNode()) ? recursive : false;
default:
return false;
}
@@ -3365,6 +3413,11 @@ bool AXObject::NameFromContents(bool recursive) const {
// containers for many subobjects. Superset of nameFrom:author ARIA roles.
case ax::mojom::Role::kAlert:
case ax::mojom::Role::kAlertDialog:
+ case ax::mojom::Role::kAnnotationAttribution:
+ case ax::mojom::Role::kAnnotationCommentary:
+ case ax::mojom::Role::kAnnotationPresence:
+ case ax::mojom::Role::kAnnotationRevision:
+ case ax::mojom::Role::kAnnotationSuggestion:
case ax::mojom::Role::kApplication:
case ax::mojom::Role::kAudio:
case ax::mojom::Role::kArticle:
@@ -3428,6 +3481,7 @@ bool AXObject::NameFromContents(bool recursive) const {
case ax::mojom::Role::kGraphicsSymbol:
case ax::mojom::Role::kGrid:
case ax::mojom::Role::kGroup:
+ case ax::mojom::Role::kHeader:
case ax::mojom::Role::kIframePresentational:
case ax::mojom::Role::kIframe:
case ax::mojom::Role::kImage:
@@ -3482,7 +3536,6 @@ bool AXObject::NameFromContents(bool recursive) const {
// Some objects can contribute their contents to ancestor names, but
// only have their own name if they are focusable
case ax::mojom::Role::kAbbr:
- case ax::mojom::Role::kAnnotation:
case ax::mojom::Role::kCanvas:
case ax::mojom::Role::kCaption:
case ax::mojom::Role::kContentDeletion:
@@ -3493,7 +3546,9 @@ bool AXObject::NameFromContents(bool recursive) const {
case ax::mojom::Role::kDetails:
case ax::mojom::Role::kFigcaption:
case ax::mojom::Role::kFooter:
+ case ax::mojom::Role::kFooterAsNonLandmark:
case ax::mojom::Role::kGenericContainer:
+ case ax::mojom::Role::kHeaderAsNonLandmark:
case ax::mojom::Role::kIgnored:
case ax::mojom::Role::kImageMap:
case ax::mojom::Role::kInlineTextBox:
@@ -3516,6 +3571,8 @@ bool AXObject::NameFromContents(bool recursive) const {
// if the row might receive focus
case ax::mojom::Role::kRow:
case ax::mojom::Role::kRuby:
+ case ax::mojom::Role::kRubyAnnotation:
+ case ax::mojom::Role::kSection:
result = recursive || (CanReceiveAccessibilityFocus() && !IsEditable());
break;