Commit 31ad4379 authored by Alice Boxhall's avatar Alice Boxhall Committed by Commit Bot

Include display:contents elements in accessibility tree

Bug: 835455
Change-Id: If3f4eacb975a8ee50a66bb787a55d257633cfb1e
Reviewed-on: https://chromium-review.googlesource.com/c/1242572
Commit-Queue: Alice Boxhall <aboxhall@chromium.org>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#609515}
parent 8f34427c
...@@ -2,6 +2,5 @@ rootWebArea pageLocation=(0, 0) ...@@ -2,6 +2,5 @@ rootWebArea pageLocation=(0, 0)
++genericContainer pageLocation=(0, 0) ++genericContainer pageLocation=(0, 0)
++++staticText pageLocation=(0, 0) name='Before' ++++staticText pageLocation=(0, 0) name='Before'
++++++inlineTextBox pageLocation=(0, 0) name='Before' ++++++inlineTextBox pageLocation=(0, 0) name='Before'
++++genericContainer pageLocation=(100, 0) ++++staticText pageLocation=(100, 0) name='After'
++++++staticText pageLocation=(100, 0) name='After' ++++++inlineTextBox pageLocation=(100, 0) name='After'
++++++++inlineTextBox pageLocation=(100, 0) name='After' \ No newline at end of file
rootWebArea rootWebArea
++genericContainer ++genericContainer
++++labelText ++++checkBox name='Checkbox Title' checkedState=false
++++++checkBox name='Checkbox Title' checkedState=false \ No newline at end of file
AXWebArea AXWebArea
++AXGroup ++AXGroup
++++AXGroup ++++AXCheckBox AXTitle='Checkbox Title' AXValue='0'
++++++AXCheckBox AXTitle='Checkbox Title' AXValue='0'
<!DOCTYPE html>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<style>
.hideAllContainers .container { display: none; }
</style>
<template id="template">
<slot role="note" name="one"></slot>
<slot name="two"></slot>
</template>
<div class="container">
<div style="display: contents" id="div">Boring old div</div>
<div style="display: contents" role="heading" id="role-heading">Heading</div>
<button style="display: contents" id="button">Clear form</button>
<a href="#" style="display: contents" id="link">Click here</a>
<div id="shadow-host">
<div slot="one">Hello</div>
<div slot="two">Goodbye</div>
</div>
</div>
<script>
let shadowHost = document.getElementById('shadow-host');
let shadowRoot = shadowHost.attachShadow({mode: 'open'});
let template = document.getElementById('template');
shadowRoot.appendChild(template.content.cloneNode(true));
</script>
<script>
test(function(t)
{
let axDiv = accessibilityController.accessibleElementById('div');
assert_not_equals(axDiv, undefined);
assert_equals(axDiv.role, 'AXRole: AXGenericContainer');
}, 'Elements with display: contents should appear in the accessibility tree.');
test(function(t)
{
let axHeading = accessibilityController.accessibleElementById('role-heading');
assert_not_equals(axHeading, undefined);
assert_equals(axHeading.role, 'AXRole: AXHeading');
}, 'Elements with display: contents should have ARIA roles respected.');
test(function(t)
{
let axButton = accessibilityController.accessibleElementById('button');
assert_not_equals(axButton, undefined);
assert_equals(axButton.role, 'AXRole: AXButton');
let axLink = accessibilityController.accessibleElementById('link');
assert_not_equals(axLink, undefined);
assert_equals(axLink.role, 'AXRole: AXLink');
}, 'Elements with display: contents should have native roles respected.');
test(function(t)
{
let axShadowHost = accessibilityController.accessibleElementById('shadow-host');
assert_equals(axShadowHost.childrenCount, 2);
let axSlotWithRole = axShadowHost.childAtIndex(0);
assert_equals(axSlotWithRole.role, 'AXRole: AXNote');
let axSlotWithoutRole = axShadowHost.childAtIndex(1);
assert_equals(axSlotWithoutRole.role, 'AXRole: AXGenericContainer');
}, '<slot> elements should appear in the accessibility tree, and have ARIA roles respected');
</script>
<script>
if (window.testRunner)
document.body.className = "hideAllContainers";
</script>
...@@ -514,9 +514,6 @@ bool AXLayoutObject::IsSelectedFromFocus() const { ...@@ -514,9 +514,6 @@ bool AXLayoutObject::IsSelectedFromFocus() const {
AXObjectInclusion AXLayoutObject::DefaultObjectInclusion( AXObjectInclusion AXLayoutObject::DefaultObjectInclusion(
IgnoredReasons* ignored_reasons) const { IgnoredReasons* ignored_reasons) const {
// The following cases can apply to any element that's a subclass of
// AXLayoutObject.
if (!layout_object_) { if (!layout_object_) {
if (ignored_reasons) if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXNotRendered)); ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
...@@ -537,20 +534,6 @@ AXObjectInclusion AXLayoutObject::DefaultObjectInclusion( ...@@ -537,20 +534,6 @@ AXObjectInclusion AXLayoutObject::DefaultObjectInclusion(
return AXObject::DefaultObjectInclusion(ignored_reasons); return AXObject::DefaultObjectInclusion(ignored_reasons);
} }
bool HasAriaAttribute(Element* element) {
if (!element)
return false;
AttributeCollection attributes = element->AttributesWithoutUpdate();
for (const Attribute& attr : attributes) {
// Attributes cache their uppercase names.
if (attr.GetName().LocalNameUpper().StartsWith("ARIA-"))
return true;
}
return false;
}
static bool HasLineBox(const LayoutBlockFlow& block_flow) { static bool HasLineBox(const LayoutBlockFlow& block_flow) {
if (!block_flow.IsLayoutNGMixin()) if (!block_flow.IsLayoutNGMixin())
return block_flow.FirstLineBox(); return block_flow.FirstLineBox();
...@@ -590,56 +573,40 @@ bool AXLayoutObject::ComputeAccessibilityIsIgnored( ...@@ -590,56 +573,40 @@ bool AXLayoutObject::ComputeAccessibilityIsIgnored(
DCHECK(initialized_); DCHECK(initialized_);
#endif #endif
if (!layout_object_) if (!layout_object_) {
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
return true; return true;
}
// Check first if any of the common reasons cause this element to be ignored. // 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 AXObjectInclusion defaultInclusion = DefaultObjectInclusion(ignored_reasons);
// roles that AXLayoutObjects take on. if (defaultInclusion == kIncludeObject)
AXObjectInclusion decision = DefaultObjectInclusion(ignored_reasons);
if (decision == kIncludeObject)
return false; return false;
if (decision == kIgnoreObject) if (defaultInclusion == kIgnoreObject)
return true;
if (layout_object_->IsAnonymousBlock() && !IsEditable())
return true; return true;
// Ignore continuations, since those are essentially duplicate copies AXObjectInclusion semanticInclusion =
// of inline nodes with blocks inside. ShouldIncludeBasedOnSemantics(ignored_reasons);
if (layout_object_->IsElementContinuation()) if (semanticInclusion == kIncludeObject)
return false;
if (semanticInclusion == kIgnoreObject)
return true; return true;
// If this element is within a parent that cannot have children, it should not if (layout_object_->IsAnonymousBlock() && !IsEditable()) {
// be exposed.
if (IsDescendantOfLeafNode()) {
if (ignored_reasons) if (ignored_reasons)
ignored_reasons->push_back( ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
IgnoredReason(kAXAncestorIsLeafNode, LeafNodeAncestor()));
return true; return true;
} }
if (RoleValue() == ax::mojom::Role::kIgnored) { // Ignore continuations, since those are essentially duplicate copies
// of inline nodes with blocks inside.
if (layout_object_->IsElementContinuation()) {
if (ignored_reasons) if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXUninteresting)); ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
return true; return true;
} }
if (HasInheritedPresentationalRole()) {
if (ignored_reasons) {
const AXObject* inherits_from = InheritsPresentationalRoleFrom();
if (inherits_from == this)
ignored_reasons->push_back(IgnoredReason(kAXPresentational));
else
ignored_reasons->push_back(
IgnoredReason(kAXInheritsPresentation, inherits_from));
}
return true;
}
if (IsTableLikeRole() || IsTableRowLikeRole() || IsTableCellLikeRole())
return false;
// A LayoutEmbeddedContent is an iframe element or embedded object element or // A LayoutEmbeddedContent is an iframe element or embedded object element or
// something like that. We don't want to ignore those. // something like that. We don't want to ignore those.
if (layout_object_->IsLayoutEmbeddedContent()) if (layout_object_->IsLayoutEmbeddedContent())
...@@ -660,145 +627,11 @@ bool AXLayoutObject::ComputeAccessibilityIsIgnored( ...@@ -660,145 +627,11 @@ bool AXLayoutObject::ComputeAccessibilityIsIgnored(
return false; 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* control_object = CorrespondingControlForLabelElement();
if (control_object && control_object->IsCheckboxOrRadio() &&
control_object->NameFromLabelElement()) {
if (ignored_reasons) {
HTMLLabelElement* label = LabelElementContainer();
if (label && label != GetNode()) {
AXObject* 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 (CanSetFocusAttribute() && GetNode() && !IsHTMLBodyElement(GetNode()))
return false;
if (IsLink())
return false;
if (IsInPageLinkTarget())
return false;
// A click handler might be placed on an otherwise ignored non-empty block
// element, e.g. a div. We shouldn't ignore such elements because if an AT
// sees the |ax::mojom::DefaultActionVerb::kClickAncestor|, it will look for
// the clickable ancestor and it expects to find one.
if (IsClickable())
return false;
if (layout_object_->IsText()) {
if (CanIgnoreTextAsEmpty()) {
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(kHeaderTag) || GetNode()->HasTagName(kFooterTag)))
return false;
// all controls are accessible
if (IsControl())
return false;
if (AriaRoleAttribute() != ax::mojom::Role::kUnknown)
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() == ax::mojom::Role::kAbbr)
return false;
// List items play an important role in defining the structure of lists. They
// should not be ignored.
if (RoleValue() == ax::mojom::Role::kListItem)
return false;
if (RoleValue() == ax::mojom::Role::kBlockquote)
return false;
if (RoleValue() == ax::mojom::Role::kDialog)
return false;
if (RoleValue() == ax::mojom::Role::kFigcaption)
return false;
if (RoleValue() == ax::mojom::Role::kFigure)
return false;
if (RoleValue() == ax::mojom::Role::kContentDeletion)
return false;
if (RoleValue() == ax::mojom::Role::kContentInsertion)
return false;
if (RoleValue() == ax::mojom::Role::kDetails)
return false;
if (RoleValue() == ax::mojom::Role::kMark)
return false;
if (RoleValue() == ax::mojom::Role::kMath)
return false;
if (RoleValue() == ax::mojom::Role::kMeter)
return false;
if (RoleValue() == ax::mojom::Role::kRuby)
return false;
if (RoleValue() == ax::mojom::Role::kSplitter)
return false;
if (RoleValue() == ax::mojom::Role::kTime)
return false;
if (RoleValue() == ax::mojom::Role::kProgressIndicator)
return false;
// if this element has aria attributes on it, it should not be ignored.
if (HasGlobalARIAAttribute())
return false;
if (IsImage())
return false;
if (IsCanvas()) { if (IsCanvas()) {
if (CanvasHasFallbackContent()) if (CanvasHasFallbackContent())
return false; return false;
const auto* canvas = ToLayoutHTMLCanvasOrNull(layout_object_); const auto* canvas = ToLayoutHTMLCanvasOrNull(GetLayoutObject());
if (canvas && if (canvas &&
(canvas->Size().Height() <= 1 || canvas->Size().Width() <= 1)) { (canvas->Size().Height() <= 1 || canvas->Size().Width() <= 1)) {
if (ignored_reasons) if (ignored_reasons)
...@@ -810,30 +643,21 @@ bool AXLayoutObject::ComputeAccessibilityIsIgnored( ...@@ -810,30 +643,21 @@ bool AXLayoutObject::ComputeAccessibilityIsIgnored(
// to decide. // to decide.
} }
if (IsWebArea() || layout_object_->IsListMarkerIncludingNG()) if (layout_object_->IsBR())
return false; return false;
// Using the title or accessibility description (so we if (layout_object_->IsText()) {
// check if there's some kind of accessible name for the element) if (CanIgnoreTextAsEmpty()) {
// to decide an element's visibility is not as definitive as if (ignored_reasons)
// previous checks, so this should remain as one of the last. ignored_reasons->push_back(IgnoredReason(kAXEmptyText));
// return true;
// 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 (HasAriaAttribute(GetElement()) || !GetAttribute(kAltAttr).IsEmpty() ||
!GetAttribute(kTitleAttr).IsEmpty())
return false; 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.
if (IsHTMLSpanElement(node)) {
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
return true;
} }
if (IsWebArea() || layout_object_->IsListMarkerIncludingNG())
return false;
// Positioned elements and scrollable containers are important for // Positioned elements and scrollable containers are important for
// determining bounding boxes. // determining bounding boxes.
if (IsScrollableContainer()) if (IsScrollableContainer())
......
...@@ -30,6 +30,8 @@ ...@@ -30,6 +30,8 @@
#include <math.h> #include <math.h>
#include "base/debug/stack_trace.h"
#include "third_party/blink/renderer/core/aom/accessible_node.h" #include "third_party/blink/renderer/core/aom/accessible_node.h"
#include "third_party/blink/renderer/core/dom/element.h" #include "third_party/blink/renderer/core/dom/element.h"
#include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
...@@ -132,23 +134,53 @@ AXObject* AXNodeObject::ActiveDescendant() { ...@@ -132,23 +134,53 @@ AXObject* AXNodeObject::ActiveDescendant() {
return ax_descendant; return ax_descendant;
} }
bool AXNodeObject::ComputeAccessibilityIsIgnored( bool HasAriaAttribute(Element* element) {
IgnoredReasons* ignored_reasons) const { if (!element)
#if DCHECK_IS_ON() return false;
// Double-check that an AXObject is never accessed before
// it's been initialized.
DCHECK(initialized_);
#endif
AttributeCollection attributes = element->AttributesWithoutUpdate();
for (const Attribute& attr : attributes) {
// Attributes cache their uppercase names.
if (attr.GetName().LocalNameUpper().StartsWith("ARIA-"))
return true;
}
return false;
}
AXObjectInclusion AXNodeObject::ShouldIncludeBasedOnSemantics(
IgnoredReasons* ignored_reasons) const {
// If this element is within a parent that cannot have children, it should not // If this element is within a parent that cannot have children, it should not
// be exposed. // be exposed.
if (IsDescendantOfLeafNode()) { if (IsDescendantOfLeafNode()) {
if (ignored_reasons) if (ignored_reasons)
ignored_reasons->push_back( ignored_reasons->push_back(
IgnoredReason(kAXAncestorIsLeafNode, LeafNodeAncestor())); IgnoredReason(kAXAncestorIsLeafNode, LeafNodeAncestor()));
return true; return kIgnoreObject;
}
if (RoleValue() == ax::mojom::Role::kIgnored) {
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
return kIgnoreObject;
}
if (HasInheritedPresentationalRole()) {
if (ignored_reasons) {
const AXObject* inherits_from = InheritsPresentationalRoleFrom();
if (inherits_from == this) {
ignored_reasons->push_back(IgnoredReason(kAXPresentational));
} else {
ignored_reasons->push_back(
IgnoredReason(kAXInheritsPresentation, inherits_from));
}
}
return kIgnoreObject;
} }
if (IsTableLikeRole() || IsTableRowLikeRole() || IsTableCellLikeRole())
return kIncludeObject;
// Ignore labels that are already referenced by a control. // Ignore labels that are already referenced by a control.
AXObject* control_object = CorrespondingControlForLabelElement(); AXObject* control_object = CorrespondingControlForLabelElement();
if (control_object && control_object->IsCheckboxOrRadio() && if (control_object && control_object->IsCheckboxOrRadio() &&
...@@ -163,24 +195,140 @@ bool AXNodeObject::ComputeAccessibilityIsIgnored( ...@@ -163,24 +195,140 @@ bool AXNodeObject::ComputeAccessibilityIsIgnored(
ignored_reasons->push_back(IgnoredReason(kAXLabelFor, control_object)); ignored_reasons->push_back(IgnoredReason(kAXLabelFor, control_object));
} }
return true; return kIgnoreObject;
} }
Element* element = GetNode()->IsElementNode() ? ToElement(GetNode()) if (CanSetFocusAttribute() && GetNode() && !IsHTMLBodyElement(GetNode()))
: GetNode()->parentElement(); return kIncludeObject;
if (!GetLayoutObject() && (!element || !element->IsInCanvasSubtree()) &&
!AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty::kHidden)) { if (IsLink() || IsInPageLinkTarget())
if (ignored_reasons) return kIncludeObject;
ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
return true; // A click handler might be placed on an otherwise ignored non-empty block
} // element, e.g. a div. We shouldn't ignore such elements because if an AT
// sees the |ax::mojom::DefaultActionVerb::kClickAncestor|, it will look for
// the clickable ancestor and it expects to find one.
if (IsClickable())
return kIncludeObject;
if (role_ == ax::mojom::Role::kUnknown) { if (IsHeading() || IsLandmarkRelated())
return kIncludeObject;
// Header and footer tags may also be exposed as landmark roles but not
// always.
if (GetNode() &&
(GetNode()->HasTagName(kHeaderTag) || GetNode()->HasTagName(kFooterTag)))
return kIncludeObject;
// All controls are accessible.
if (IsControl())
return kIncludeObject;
// Anything with an explicit ARIA role should be included.
if (AriaRoleAttribute() != ax::mojom::Role::kUnknown)
return kIncludeObject;
// Don't ignore labels, because they serve as TitleUIElements.
Node* node = GetNode();
if (IsHTMLLabelElement(node))
return kIncludeObject;
// 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 kIncludeObject;
static const std::set<ax::mojom::Role> always_included_computed_roles = {
ax::mojom::Role::kAbbr,
ax::mojom::Role::kBlockquote,
ax::mojom::Role::kContentDeletion,
ax::mojom::Role::kContentInsertion,
ax::mojom::Role::kDetails,
ax::mojom::Role::kDialog,
ax::mojom::Role::kFigcaption,
ax::mojom::Role::kFigure,
ax::mojom::Role::kListItem,
ax::mojom::Role::kMark,
ax::mojom::Role::kMath,
ax::mojom::Role::kMeter,
ax::mojom::Role::kProgressIndicator,
ax::mojom::Role::kRuby,
ax::mojom::Role::kSplitter,
ax::mojom::Role::kTime,
};
if (always_included_computed_roles.find(RoleValue()) !=
always_included_computed_roles.end())
return kIncludeObject;
// If this element has aria attributes on it, it should not be ignored.
if (HasGlobalARIAAttribute())
return kIncludeObject;
if (IsImage())
return kIncludeObject;
// Using the 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 (HasAriaAttribute(GetElement()) || !GetAttribute(kAltAttr).IsEmpty() ||
!GetAttribute(kTitleAttr).IsEmpty())
return kIncludeObject;
// <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.
if (node && IsHTMLSpanElement(node)) {
if (ignored_reasons) if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXUninteresting)); ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
return kIgnoreObject;
}
return kDefaultBehavior;
}
bool AXNodeObject::ComputeAccessibilityIsIgnored(
IgnoredReasons* ignored_reasons) const {
#if DCHECK_IS_ON()
// Double-check that an AXObject is never accessed before
// it's been initialized.
DCHECK(initialized_);
#endif
if (GetLayoutObject()) {
if (role_ == ax::mojom::Role::kUnknown) {
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXUninteresting));
return true;
}
return false;
}
Element* element = GetNode()->IsElementNode() ? ToElement(GetNode())
: GetNode()->parentElement();
if (!element)
return true; return true;
if (element->IsInCanvasSubtree())
return ShouldIncludeBasedOnSemantics(ignored_reasons) == kIgnoreObject;
if (AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty::kHidden))
return false;
if (element->HasDisplayContentsStyle()) {
if (ShouldIncludeBasedOnSemantics(ignored_reasons) == kIncludeObject)
return false;
} }
return false;
if (ignored_reasons)
ignored_reasons->push_back(IgnoredReason(kAXNotRendered));
return true;
} }
static bool IsListElement(Node* node) { static bool IsListElement(Node* node) {
...@@ -2099,9 +2247,12 @@ void AXNodeObject::GetRelativeBounds(AXObject** out_container, ...@@ -2099,9 +2247,12 @@ void AXNodeObject::GetRelativeBounds(AXObject** out_container,
} }
} }
// If it's in a canvas but doesn't have an explicit rect, get the bounding Element* element = GetElement();
// rect of its children. // If it's in a canvas but doesn't have an explicit rect, or has display:
if (GetNode()->parentElement()->IsInCanvasSubtree()) { // contents set, get the bounding rect of its children.
if ((GetNode()->parentElement() &&
GetNode()->parentElement()->IsInCanvasSubtree()) ||
(element && element->HasDisplayContentsStyle())) {
Vector<FloatRect> rects; Vector<FloatRect> rects;
for (Node& child : NodeTraversal::ChildrenOf(*GetNode())) { for (Node& child : NodeTraversal::ChildrenOf(*GetNode())) {
if (child.IsHTMLElement()) { if (child.IsHTMLElement()) {
......
...@@ -55,6 +55,8 @@ class MODULES_EXPORT AXNodeObject : public AXObject { ...@@ -55,6 +55,8 @@ class MODULES_EXPORT AXNodeObject : public AXObject {
// The accessibility role, not taking ARIA into account. // The accessibility role, not taking ARIA into account.
ax::mojom::Role native_role_; ax::mojom::Role native_role_;
AXObjectInclusion ShouldIncludeBasedOnSemantics(
IgnoredReasons* = nullptr) const;
bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override; bool ComputeAccessibilityIsIgnored(IgnoredReasons* = nullptr) const override;
const AXObject* InheritsPresentationalRoleFrom() const override; const AXObject* InheritsPresentationalRoleFrom() const override;
ax::mojom::Role DetermineAccessibilityRole() override; ax::mojom::Role DetermineAccessibilityRole() override;
......
...@@ -1011,8 +1011,12 @@ void AXObjectCacheImpl::HandlePossibleRoleChange(Node* node) { ...@@ -1011,8 +1011,12 @@ void AXObjectCacheImpl::HandlePossibleRoleChange(Node* node) {
if (!node) if (!node)
return; // Virtual AOM node. return; // Virtual AOM node.
AXObject* obj = Get(node);
if (!obj && IsHTMLSelectElement(node))
obj = GetOrCreate(node);
// Invalidate the current object and make the parent reconsider its children. // Invalidate the current object and make the parent reconsider its children.
if (AXObject* obj = Get(node)) { if (obj) {
// Save parent for later use. // Save parent for later use.
AXObject* parent = obj->ParentObject(); AXObject* parent = obj->ParentObject();
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment