summaryrefslogtreecommitdiff
path: root/chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc
diff options
context:
space:
mode:
authorAllan Sandfeld Jensen <allan.jensen@qt.io>2021-09-03 13:32:17 +0200
committerAllan Sandfeld Jensen <allan.jensen@qt.io>2021-10-01 14:31:55 +0200
commit21ba0c5d4bf8fba15dddd97cd693bad2358b77fd (patch)
tree91be119f694044dfc1ff9fdc054459e925de9df0 /chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc
parent03c549e0392f92c02536d3f86d5e1d8dfa3435ac (diff)
downloadqtwebengine-chromium-21ba0c5d4bf8fba15dddd97cd693bad2358b77fd.tar.gz
BASELINE: Update Chromium to 92.0.4515.166
Change-Id: I42a050486714e9e54fc271f2a8939223a02ae364
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.cc1386
1 files changed, 729 insertions, 657 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 d09d11af243..fa0a78a291f 100644
--- a/chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc
+++ b/chromium/third_party/blink/renderer/modules/accessibility/ax_object.cc
@@ -29,6 +29,7 @@
#include "third_party/blink/renderer/modules/accessibility/ax_object.h"
#include <algorithm>
+#include <ostream>
#include "base/strings/string_util.h"
#include "build/build_config.h"
#include "third_party/blink/public/common/input/web_menu_source_type.h"
@@ -41,6 +42,7 @@
#include "third_party/blink/renderer/core/dom/events/simulated_click_options.h"
#include "third_party/blink/renderer/core/dom/focus_params.h"
#include "third_party/blink/renderer/core/dom/node_computed_style.h"
+#include "third_party/blink/renderer/core/editing/editing_utilities.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/local_frame_view.h"
#include "third_party/blink/renderer/core/frame/settings.h"
@@ -48,11 +50,14 @@
#include "third_party/blink/renderer/core/html/custom/element_internals.h"
#include "third_party/blink/renderer/core/html/forms/html_input_element.h"
#include "third_party/blink/renderer/core/html/forms/html_select_element.h"
+#include "third_party/blink/renderer/core/html/forms/html_text_area_element.h"
#include "third_party/blink/renderer/core/html/forms/text_control_element.h"
#include "third_party/blink/renderer/core/html/html_dialog_element.h"
+#include "third_party/blink/renderer/core/html/html_element.h"
#include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
#include "third_party/blink/renderer/core/html/html_head_element.h"
#include "third_party/blink/renderer/core/html/html_script_element.h"
+#include "third_party/blink/renderer/core/html/html_slot_element.h"
#include "third_party/blink/renderer/core/html/html_style_element.h"
#include "third_party/blink/renderer/core/html/html_table_cell_element.h"
#include "third_party/blink/renderer/core/html/html_table_element.h"
@@ -73,6 +78,7 @@
#include "third_party/blink/renderer/core/svg/svg_element.h"
#include "third_party/blink/renderer/core/svg/svg_g_element.h"
#include "third_party/blink/renderer/core/svg/svg_style_element.h"
+#include "third_party/blink/renderer/modules/accessibility/ax_image_map_link.h"
#include "third_party/blink/renderer/modules/accessibility/ax_menu_list.h"
#include "third_party/blink/renderer/modules/accessibility/ax_menu_list_option.h"
#include "third_party/blink/renderer/modules/accessibility/ax_menu_list_popup.h"
@@ -109,8 +115,6 @@ String IgnoredReasonName(AXIgnoredReason reason) {
return "activeModalDialog";
case kAXAriaModalDialog:
return "activeAriaModalDialog";
- case kAXAncestorIsLeafNode:
- return "ancestorIsLeafNode";
case kAXAriaHiddenElement:
return "ariaHiddenElement";
case kAXAriaHiddenSubtree:
@@ -123,8 +127,6 @@ String IgnoredReasonName(AXIgnoredReason reason) {
return "inertElement";
case kAXInertSubtree:
return "inertSubtree";
- case kAXInheritsPresentation:
- return "inheritsPresentation";
case kAXLabelContainer:
return "labelContainer";
case kAXLabelFor:
@@ -159,7 +161,15 @@ String GetIgnoredReasonsDebugString(AXObject::IgnoredReasons& reasons) {
#endif
-String GetElementString(Element* element) {
+String GetNodeString(Node* node) {
+ if (node->IsTextNode()) {
+ String string_builder = "\"";
+ string_builder = string_builder + node->nodeValue();
+ string_builder = string_builder + "\"";
+ return string_builder;
+ }
+
+ Element* element = DynamicTo<Element>(node);
if (!element)
return "<null>";
@@ -183,14 +193,18 @@ Node* GetParentNodeForComputeParent(Node* node) {
if (!node)
return nullptr;
- // Prefer LayoutTreeBuilderTraversal::Parent(), which handles pseudo content.
- Node* parent = LayoutTreeBuilderTraversal::Parent(*node);
- if (parent)
- return parent;
-
- // Unfortunately, LayoutTreeBuilderTraversal::Parent() can return nullptr for
- // a text node, such as inside a text area. Fall back on DOM parentNode().
- return node->parentNode();
+ // Use LayoutTreeBuilderTraversal::Parent(), which handles pseudo content.
+ // This can return nullptr for a node that is never visited by
+ // LayoutTreeBuilderTraversal's child traversal. For example, while an element
+ // can be appended as a <textarea>'s child, it is never visited by
+ // LayoutTreeBuilderTraversal's child traversal. Therefore, returning null in
+ // this case is appropriate, because that child content is not attached to any
+ // parent as far as rendering or accessibility are concerned.
+ // Whenever null is returned from this function, then a parent cannot be
+ // computed, and when a parent is not provided or computed, the accessible
+ // object will not be created.
+ // TODO(aleventhal) Remove this method / inline, if proven to be this simple.
+ return LayoutTreeBuilderTraversal::Parent(*node);
}
#if DCHECK_IS_ON()
@@ -202,7 +216,6 @@ bool IsValidRole(ax::mojom::blink::Role role) {
case ax::mojom::blink::Role::kColumn:
case ax::mojom::blink::Role::kDesktop:
case ax::mojom::blink::Role::kKeyboard:
- case ax::mojom::blink::Role::kIgnored:
case ax::mojom::blink::Role::kImeCandidate:
case ax::mojom::blink::Role::kListGrid:
case ax::mojom::blink::Role::kPane:
@@ -227,6 +240,9 @@ struct RoleHashTraits : HashTraits<ax::mojom::blink::Role> {
}
};
+constexpr wtf_size_t kNumRoles =
+ static_cast<wtf_size_t>(ax::mojom::blink::Role::kMaxValue) + 1;
+
using ARIARoleMap = HashMap<String,
ax::mojom::blink::Role,
CaseFoldingHash,
@@ -234,12 +250,16 @@ using ARIARoleMap = HashMap<String,
RoleHashTraits>;
struct RoleEntry {
- const char* aria_role;
- ax::mojom::blink::Role webcore_role;
+ const char* role_name;
+ ax::mojom::blink::Role role;
};
// Mapping of ARIA role name to internal role name.
-const RoleEntry kRoles[] = {
+// This is used for the following:
+// 1. Map from an ARIA role to the internal role when building tree.
+// 2. Map from an internal role to an ARIA role name, for debugging, the
+// xml-roles object attribute and element.computedRole.
+const RoleEntry kAriaRoles[] = {
{"alert", ax::mojom::blink::Role::kAlert},
{"alertdialog", ax::mojom::blink::Role::kAlertDialog},
{"application", ax::mojom::blink::Role::kApplication},
@@ -342,16 +362,17 @@ const RoleEntry kRoles[] = {
{"mark", ax::mojom::blink::Role::kMark},
{"meter", ax::mojom::blink::Role::kMeter},
{"navigation", ax::mojom::blink::Role::kNavigation},
+ // role="presentation" is the same as role="none".
+ {"presentation", ax::mojom::blink::Role::kNone},
+ // role="none" is listed after role="presentation", so that it is the
+ // canonical name in devtools and tests.
{"none", ax::mojom::blink::Role::kNone},
{"note", ax::mojom::blink::Role::kNote},
{"option", ax::mojom::blink::Role::kListBoxOption},
{"paragraph", ax::mojom::blink::Role::kParagraph},
- {"presentation", ax::mojom::blink::Role::kPresentational},
{"progressbar", ax::mojom::blink::Role::kProgressIndicator},
{"radio", ax::mojom::blink::Role::kRadioButton},
{"radiogroup", ax::mojom::blink::Role::kRadioGroup},
- // TODO(accessibility) region should only be mapped
- // if name present. See http://crbug.com/840819.
{"region", ax::mojom::blink::Role::kRegion},
{"row", ax::mojom::blink::Role::kRow},
{"rowgroup", ax::mojom::blink::Role::kRowGroup},
@@ -371,7 +392,6 @@ const RoleEntry kRoles[] = {
{"tablist", ax::mojom::blink::Role::kTabList},
{"tabpanel", ax::mojom::blink::Role::kTabPanel},
{"term", ax::mojom::blink::Role::kTerm},
- {"text", ax::mojom::blink::Role::kStaticText},
{"textbox", ax::mojom::blink::Role::kTextField},
{"time", ax::mojom::blink::Role::kTime},
{"timer", ax::mojom::blink::Role::kTimer},
@@ -381,316 +401,49 @@ const RoleEntry kRoles[] = {
{"treegrid", ax::mojom::blink::Role::kTreeGrid},
{"treeitem", ax::mojom::blink::Role::kTreeItem}};
-struct InternalRoleEntry {
- ax::mojom::blink::Role webcore_role;
- const char* internal_role_name;
-};
-
-const InternalRoleEntry kInternalRoles[] = {
- {ax::mojom::blink::Role::kNone, "None"},
- {ax::mojom::blink::Role::kAbbr, "Abbr"},
- {ax::mojom::blink::Role::kAlertDialog, "AlertDialog"},
- {ax::mojom::blink::Role::kAlert, "Alert"},
- {ax::mojom::blink::Role::kAnchor, "Anchor"},
- {ax::mojom::blink::Role::kComment, "Comment"},
- {ax::mojom::blink::Role::kApplication, "Application"},
- {ax::mojom::blink::Role::kArticle, "Article"},
- {ax::mojom::blink::Role::kAudio, "Audio"},
- {ax::mojom::blink::Role::kBanner, "Banner"},
- {ax::mojom::blink::Role::kBlockquote, "Blockquote"},
- {ax::mojom::blink::Role::kButton, "Button"},
- {ax::mojom::blink::Role::kCanvas, "Canvas"},
- {ax::mojom::blink::Role::kCaption, "Caption"},
- {ax::mojom::blink::Role::kCaret, "Caret"},
- {ax::mojom::blink::Role::kCell, "Cell"},
- {ax::mojom::blink::Role::kCheckBox, "CheckBox"},
- {ax::mojom::blink::Role::kClient, "Client"},
- {ax::mojom::blink::Role::kCode, "Code"},
- {ax::mojom::blink::Role::kColorWell, "ColorWell"},
- {ax::mojom::blink::Role::kColumnHeader, "ColumnHeader"},
- {ax::mojom::blink::Role::kColumn, "Column"},
- {ax::mojom::blink::Role::kComboBoxGrouping, "ComboBox"},
- {ax::mojom::blink::Role::kComboBoxMenuButton, "ComboBox"},
- {ax::mojom::blink::Role::kComplementary, "Complementary"},
- {ax::mojom::blink::Role::kContentDeletion, "ContentDeletion"},
- {ax::mojom::blink::Role::kContentInsertion, "ContentInsertion"},
- {ax::mojom::blink::Role::kContentInfo, "ContentInfo"},
- {ax::mojom::blink::Role::kDate, "Date"},
- {ax::mojom::blink::Role::kDateTime, "DateTime"},
- {ax::mojom::blink::Role::kDefinition, "Definition"},
- {ax::mojom::blink::Role::kDescriptionListDetail, "DescriptionListDetail"},
- {ax::mojom::blink::Role::kDescriptionList, "DescriptionList"},
- {ax::mojom::blink::Role::kDescriptionListTerm, "DescriptionListTerm"},
- {ax::mojom::blink::Role::kDesktop, "Desktop"},
- {ax::mojom::blink::Role::kDetails, "Details"},
- {ax::mojom::blink::Role::kDialog, "Dialog"},
- {ax::mojom::blink::Role::kDirectory, "Directory"},
- {ax::mojom::blink::Role::kDisclosureTriangle, "DisclosureTriangle"},
- // --------------------------------------------------------------
- // DPub Roles:
- // https://www.w3.org/TR/dpub-aam-1.0/#mapping_role_table
- {ax::mojom::blink::Role::kDocAbstract, "DocAbstract"},
- {ax::mojom::blink::Role::kDocAcknowledgments, "DocAcknowledgments"},
- {ax::mojom::blink::Role::kDocAfterword, "DocAfterword"},
- {ax::mojom::blink::Role::kDocAppendix, "DocAppendix"},
- {ax::mojom::blink::Role::kDocBackLink, "DocBackLink"},
- {ax::mojom::blink::Role::kDocBiblioEntry, "DocBiblioentry"},
- {ax::mojom::blink::Role::kDocBibliography, "DocBibliography"},
- {ax::mojom::blink::Role::kDocBiblioRef, "DocBiblioref"},
- {ax::mojom::blink::Role::kDocChapter, "DocChapter"},
- {ax::mojom::blink::Role::kDocColophon, "DocColophon"},
- {ax::mojom::blink::Role::kDocConclusion, "DocConclusion"},
- {ax::mojom::blink::Role::kDocCover, "DocCover"},
- {ax::mojom::blink::Role::kDocCredit, "DocCredit"},
- {ax::mojom::blink::Role::kDocCredits, "DocCredits"},
- {ax::mojom::blink::Role::kDocDedication, "DocDedication"},
- {ax::mojom::blink::Role::kDocEndnote, "DocEndnote"},
- {ax::mojom::blink::Role::kDocEndnotes, "DocEndnotes"},
- {ax::mojom::blink::Role::kDocEpigraph, "DocEpigraph"},
- {ax::mojom::blink::Role::kDocEpilogue, "DocEpilogue"},
- {ax::mojom::blink::Role::kDocErrata, "DocErrata"},
- {ax::mojom::blink::Role::kDocExample, "DocExample"},
- {ax::mojom::blink::Role::kDocFootnote, "DocFootnote"},
- {ax::mojom::blink::Role::kDocForeword, "DocForeword"},
- {ax::mojom::blink::Role::kDocGlossary, "DocGlossary"},
- {ax::mojom::blink::Role::kDocGlossRef, "DocGlossref"},
- {ax::mojom::blink::Role::kDocIndex, "DocIndex"},
- {ax::mojom::blink::Role::kDocIntroduction, "DocIntroduction"},
- {ax::mojom::blink::Role::kDocNoteRef, "DocNoteref"},
- {ax::mojom::blink::Role::kDocNotice, "DocNotice"},
- {ax::mojom::blink::Role::kDocPageBreak, "DocPagebreak"},
- {ax::mojom::blink::Role::kDocPageFooter, "DocPageFooter"},
- {ax::mojom::blink::Role::kDocPageHeader, "DocPageHeader"},
- {ax::mojom::blink::Role::kDocPageList, "DocPagelist"},
- {ax::mojom::blink::Role::kDocPart, "DocPart"},
- {ax::mojom::blink::Role::kDocPreface, "DocPreface"},
- {ax::mojom::blink::Role::kDocPrologue, "DocPrologue"},
- {ax::mojom::blink::Role::kDocPullquote, "DocPullquote"},
- {ax::mojom::blink::Role::kDocQna, "DocQna"},
- {ax::mojom::blink::Role::kDocSubtitle, "DocSubtitle"},
- {ax::mojom::blink::Role::kDocTip, "DocTip"},
- {ax::mojom::blink::Role::kDocToc, "DocToc"},
- // End DPub roles.
- // --------------------------------------------------------------
- {ax::mojom::blink::Role::kDocument, "Document"},
- {ax::mojom::blink::Role::kEmbeddedObject, "EmbeddedObject"},
- {ax::mojom::blink::Role::kEmphasis, "Emphasis"},
- {ax::mojom::blink::Role::kFeed, "feed"},
- {ax::mojom::blink::Role::kFigcaption, "Figcaption"},
- {ax::mojom::blink::Role::kFigure, "Figure"},
- {ax::mojom::blink::Role::kFooter, "Footer"},
- {ax::mojom::blink::Role::kFooterAsNonLandmark, "FooterAsNonLandmark"},
- {ax::mojom::blink::Role::kForm, "Form"},
- {ax::mojom::blink::Role::kGenericContainer, "GenericContainer"},
- // --------------------------------------------------------------
- // ARIA Graphics module roles:
- // https://rawgit.com/w3c/graphics-aam/master/#mapping_role_table
- {ax::mojom::blink::Role::kGraphicsDocument, "GraphicsDocument"},
- {ax::mojom::blink::Role::kGraphicsObject, "GraphicsObject"},
- {ax::mojom::blink::Role::kGraphicsSymbol, "GraphicsSymbol"},
- // End ARIA Graphics module roles.
- // --------------------------------------------------------------
- {ax::mojom::blink::Role::kGrid, "Grid"},
- {ax::mojom::blink::Role::kGroup, "Group"},
- {ax::mojom::blink::Role::kHeader, "Header"},
- {ax::mojom::blink::Role::kHeaderAsNonLandmark, "HeaderAsNonLandmark"},
- {ax::mojom::blink::Role::kHeading, "Heading"},
- {ax::mojom::blink::Role::kIframePresentational, "IframePresentational"},
- {ax::mojom::blink::Role::kIframe, "Iframe"},
- {ax::mojom::blink::Role::kIgnored, "Ignored"},
- {ax::mojom::blink::Role::kImage, "Image"},
- {ax::mojom::blink::Role::kImeCandidate, "ImeCandidate"},
- {ax::mojom::blink::Role::kInlineTextBox, "InlineTextBox"},
- {ax::mojom::blink::Role::kInputTime, "InputTime"},
- {ax::mojom::blink::Role::kKeyboard, "Keyboard"},
- {ax::mojom::blink::Role::kLabelText, "Label"},
- {ax::mojom::blink::Role::kLayoutTable, "LayoutTable"},
- {ax::mojom::blink::Role::kLayoutTableCell, "LayoutCellTable"},
- {ax::mojom::blink::Role::kLayoutTableRow, "LayoutRowTable"},
- {ax::mojom::blink::Role::kLegend, "Legend"},
- {ax::mojom::blink::Role::kLink, "Link"},
- {ax::mojom::blink::Role::kLineBreak, "LineBreak"},
- {ax::mojom::blink::Role::kListBox, "ListBox"},
- {ax::mojom::blink::Role::kListBoxOption, "ListBoxOption"},
- {ax::mojom::blink::Role::kListGrid, "ListGrid"},
- {ax::mojom::blink::Role::kListItem, "ListItem"},
- {ax::mojom::blink::Role::kListMarker, "ListMarker"},
- {ax::mojom::blink::Role::kList, "List"},
- {ax::mojom::blink::Role::kLog, "Log"},
- {ax::mojom::blink::Role::kMain, "Main"},
- {ax::mojom::blink::Role::kMark, "Mark"},
- {ax::mojom::blink::Role::kMarquee, "Marquee"},
- {ax::mojom::blink::Role::kMath, "Math"},
- {ax::mojom::blink::Role::kMenuBar, "MenuBar"},
- {ax::mojom::blink::Role::kMenuItem, "MenuItem"},
- {ax::mojom::blink::Role::kMenuItemCheckBox, "MenuItemCheckBox"},
- {ax::mojom::blink::Role::kMenuItemRadio, "MenuItemRadio"},
- {ax::mojom::blink::Role::kMenuListOption, "MenuListOption"},
- {ax::mojom::blink::Role::kMenuListPopup, "MenuListPopup"},
- {ax::mojom::blink::Role::kMenu, "Menu"},
- {ax::mojom::blink::Role::kMeter, "Meter"},
- {ax::mojom::blink::Role::kNavigation, "Navigation"},
- {ax::mojom::blink::Role::kNote, "Note"},
- {ax::mojom::blink::Role::kPane, "Pane"},
- {ax::mojom::blink::Role::kParagraph, "Paragraph"},
- {ax::mojom::blink::Role::kPdfActionableHighlight, "PdfActionableHighlight"},
- {ax::mojom::blink::Role::kPdfRoot, "PdfRoot"},
- {ax::mojom::blink::Role::kPluginObject, "PluginObject"},
- {ax::mojom::blink::Role::kPopUpButton, "PopUpButton"},
- {ax::mojom::blink::Role::kPortal, "Portal"},
- {ax::mojom::blink::Role::kPre, "Pre"},
- {ax::mojom::blink::Role::kPresentational, "Presentational"},
- {ax::mojom::blink::Role::kProgressIndicator, "ProgressIndicator"},
- {ax::mojom::blink::Role::kRadioButton, "RadioButton"},
- {ax::mojom::blink::Role::kRadioGroup, "RadioGroup"},
- {ax::mojom::blink::Role::kRegion, "Region"},
- {ax::mojom::blink::Role::kRootWebArea, "WebArea"},
- {ax::mojom::blink::Role::kRow, "Row"},
- {ax::mojom::blink::Role::kRowGroup, "RowGroup"},
- {ax::mojom::blink::Role::kRowHeader, "RowHeader"},
- {ax::mojom::blink::Role::kRuby, "Ruby"},
- {ax::mojom::blink::Role::kRubyAnnotation, "RubyAnnotation"},
- {ax::mojom::blink::Role::kSection, "Section"},
- {ax::mojom::blink::Role::kSvgRoot, "SVGRoot"},
- {ax::mojom::blink::Role::kScrollBar, "ScrollBar"},
- {ax::mojom::blink::Role::kScrollView, "ScrollView"},
- {ax::mojom::blink::Role::kSearch, "Search"},
- {ax::mojom::blink::Role::kSearchBox, "SearchBox"},
- {ax::mojom::blink::Role::kSlider, "Slider"},
- {ax::mojom::blink::Role::kSpinButton, "SpinButton"},
- {ax::mojom::blink::Role::kSplitter, "Splitter"},
- {ax::mojom::blink::Role::kStaticText, "StaticText"},
- {ax::mojom::blink::Role::kStatus, "Status"},
- {ax::mojom::blink::Role::kStrong, "Strong"},
- {ax::mojom::blink::Role::kSuggestion, "Suggestion"},
- {ax::mojom::blink::Role::kSwitch, "Switch"},
- {ax::mojom::blink::Role::kTab, "Tab"},
- {ax::mojom::blink::Role::kTabList, "TabList"},
- {ax::mojom::blink::Role::kTabPanel, "TabPanel"},
- {ax::mojom::blink::Role::kTable, "Table"},
- {ax::mojom::blink::Role::kTableHeaderContainer, "TableHeaderContainer"},
- {ax::mojom::blink::Role::kTerm, "Term"},
- {ax::mojom::blink::Role::kTextField, "TextField"},
- {ax::mojom::blink::Role::kTextFieldWithComboBox, "ComboBox"},
- {ax::mojom::blink::Role::kTime, "Time"},
- {ax::mojom::blink::Role::kTimer, "Timer"},
- {ax::mojom::blink::Role::kTitleBar, "TitleBar"},
- {ax::mojom::blink::Role::kToggleButton, "ToggleButton"},
- {ax::mojom::blink::Role::kToolbar, "Toolbar"},
- {ax::mojom::blink::Role::kTreeGrid, "TreeGrid"},
- {ax::mojom::blink::Role::kTreeItem, "TreeItem"},
- {ax::mojom::blink::Role::kTree, "Tree"},
- {ax::mojom::blink::Role::kTooltip, "UserInterfaceTooltip"},
- {ax::mojom::blink::Role::kUnknown, "Unknown"},
- {ax::mojom::blink::Role::kVideo, "Video"},
- {ax::mojom::blink::Role::kWebView, "WebView"},
- {ax::mojom::blink::Role::kWindow, "Window"}};
-
-static_assert(base::size(kInternalRoles) ==
- static_cast<size_t>(ax::mojom::blink::Role::kMaxValue) + 1,
- "Not all internal roles have an entry in internalRoles array");
-
-// Roles which we need to map in the other direction
+// More friendly names for debugging. These are roles which don't map from
+// the ARIA role name to the internal role when building the tree, but when
+// debugging, we want to show the ARIA role name, since it is close in meaning.
const RoleEntry kReverseRoles[] = {
{"banner", ax::mojom::blink::Role::kHeader},
{"button", ax::mojom::blink::Role::kToggleButton},
{"combobox", ax::mojom::blink::Role::kPopUpButton},
{"contentinfo", ax::mojom::blink::Role::kFooter},
{"menuitem", ax::mojom::blink::Role::kMenuListOption},
- {"progressbar", ax::mojom::blink::Role::kMeter},
- {"region", ax::mojom::blink::Role::kSection},
- {"textbox", ax::mojom::blink::Role::kTextField},
{"combobox", ax::mojom::blink::Role::kComboBoxMenuButton},
{"combobox", ax::mojom::blink::Role::kTextFieldWithComboBox}};
static ARIARoleMap* CreateARIARoleMap() {
ARIARoleMap* role_map = new ARIARoleMap;
- for (size_t i = 0; i < base::size(kRoles); ++i)
- role_map->Set(String(kRoles[i].aria_role), kRoles[i].webcore_role);
+ for (auto aria_role : kAriaRoles)
+ role_map->Set(String(aria_role.role_name), aria_role.role);
return role_map;
}
-static Vector<AtomicString>* CreateRoleNameVector() {
- Vector<AtomicString>* role_name_vector =
- new Vector<AtomicString>(base::size(kInternalRoles));
- for (wtf_size_t i = 0; i < base::size(kInternalRoles); i++)
- (*role_name_vector)[i] = g_null_atom;
+// The role name vector contains only ARIA roles, and no internal roles.
+static Vector<AtomicString>* CreateARIARoleNameVector() {
+ Vector<AtomicString>* role_name_vector = new Vector<AtomicString>(kNumRoles);
+ role_name_vector->Fill(g_null_atom, kNumRoles);
- for (wtf_size_t i = 0; i < base::size(kRoles); ++i) {
- (*role_name_vector)[static_cast<wtf_size_t>(kRoles[i].webcore_role)] =
- AtomicString(kRoles[i].aria_role);
+ for (auto aria_role : kAriaRoles) {
+ (*role_name_vector)[static_cast<wtf_size_t>(aria_role.role)] =
+ AtomicString(aria_role.role_name);
}
- for (wtf_size_t i = 0; i < base::size(kReverseRoles); ++i) {
- (*role_name_vector)[static_cast<wtf_size_t>(
- kReverseRoles[i].webcore_role)] =
- AtomicString(kReverseRoles[i].aria_role);
+ for (auto reverse_role : kReverseRoles) {
+ (*role_name_vector)[static_cast<wtf_size_t>(reverse_role.role)] =
+ AtomicString(reverse_role.role_name);
}
return role_name_vector;
}
-static Vector<AtomicString>* CreateInternalRoleNameVector() {
- Vector<AtomicString>* internal_role_name_vector =
- new Vector<AtomicString>(base::size(kInternalRoles));
- for (wtf_size_t i = 0; i < base::size(kInternalRoles); i++) {
- (*internal_role_name_vector)[static_cast<wtf_size_t>(
- kInternalRoles[i].webcore_role)] =
- AtomicString(kInternalRoles[i].internal_role_name);
- }
-
- return internal_role_name_vector;
-}
-
HTMLDialogElement* GetActiveDialogElement(Node* node) {
return node->GetDocument().ActiveModalDialog();
}
-// TODO(dmazzoni): replace this with a call to RoleName().
-std::string GetEquivalentAriaRoleString(const ax::mojom::blink::Role role) {
- switch (role) {
- case ax::mojom::blink::Role::kArticle:
- return "article";
- case ax::mojom::blink::Role::kBanner:
- return "banner";
- case ax::mojom::blink::Role::kButton:
- return "button";
- case ax::mojom::blink::Role::kComplementary:
- return "complementary";
- case ax::mojom::blink::Role::kFigure:
- return "figure";
- case ax::mojom::blink::Role::kFooter:
- return "contentinfo";
- case ax::mojom::blink::Role::kHeader:
- return "banner";
- case ax::mojom::blink::Role::kHeading:
- return "heading";
- case ax::mojom::blink::Role::kImage:
- return "img";
- case ax::mojom::blink::Role::kMain:
- return "main";
- case ax::mojom::blink::Role::kNavigation:
- return "navigation";
- case ax::mojom::blink::Role::kRadioButton:
- return "radio";
- case ax::mojom::blink::Role::kRegion:
- return "region";
- case ax::mojom::blink::Role::kSection:
- // A <section> element uses the 'region' ARIA role mapping.
- return "region";
- case ax::mojom::blink::Role::kSlider:
- return "slider";
- case ax::mojom::blink::Role::kTime:
- return "time";
- default:
- break;
- }
-
- return std::string();
-}
-
} // namespace
int32_t ToAXMarkerType(DocumentMarker::MarkerType marker_type) {
@@ -720,6 +473,8 @@ int32_t ToAXMarkerType(DocumentMarker::MarkerType marker_type) {
return static_cast<int32_t>(result);
}
+// static
+bool AXObject::is_loading_inline_boxes_ = false;
unsigned AXObject::number_of_live_ax_objects_ = 0;
AXObject::AXObject(AXObjectCacheImpl& ax_object_cache)
@@ -731,10 +486,7 @@ AXObject::AXObject(AXObjectCacheImpl& ax_object_cache)
cached_is_ignored_(false),
cached_is_ignored_but_included_in_tree_(false),
cached_is_inert_or_aria_hidden_(false),
- cached_is_descendant_of_leaf_node_(false),
cached_is_descendant_of_disabled_node_(false),
- cached_has_inherited_presentational_role_(false),
- cached_is_editable_root_(false),
cached_live_region_root_(nullptr),
cached_aria_column_index_(0),
cached_aria_row_index_(0),
@@ -747,9 +499,12 @@ AXObject::~AXObject() {
--number_of_live_ax_objects_;
}
-void AXObject::Init(AXObject* parent_if_known) {
+void AXObject::Init(AXObject* parent) {
#if DCHECK_IS_ON()
- DCHECK(!parent_);
+ DCHECK(!parent_) << "Should not already have a cached parent:"
+ << "\n* Child = " << GetNode() << " / " << GetLayoutObject()
+ << "\n* Parent = " << parent_->ToString(true, true)
+ << "\n* Equal to passed-in parent? " << (parent == parent_);
DCHECK(!is_initializing_);
base::AutoReset<bool> reentrancy_protector(&is_initializing_, true);
#endif // DCHECK_IS_ON()
@@ -766,24 +521,42 @@ void AXObject::Init(AXObject* parent_if_known) {
// Determine the parent as soon as possible.
// Every AXObject must have a parent unless it's the root.
- SetParent(parent_if_known ? parent_if_known : ComputeParent());
- DCHECK(parent_ || IsA<Document>(GetNode()))
+ SetParent(parent);
+ DCHECK(parent_ || IsRoot())
<< "The following node should have a parent: " << GetNode();
- SetNeedsToUpdateChildren(); // Should be called after role_ is set.
+ // The parent cannot have children. This object must be destroyed.
+ DCHECK(!parent_ || parent_->CanHaveChildren())
+ << "Tried to set a parent that cannot have children:"
+ << "\n* Parent = " << parent_->ToString(true, true)
+ << "\n* Child = " << ToString(true, true);
+
+ // This is one after the role_ is computed, because the role is used to
+ // determine whether an AXObject can have children.
+ children_dirty_ = CanHaveChildren();
+
+ // Ensure that the aria-owns relationship is set before attempting
+ // to update cached attribute values.
+ if (GetNode())
+ AXObjectCache().MaybeNewRelationTarget(*GetNode(), this);
+
UpdateCachedAttributeValuesIfNeeded(false);
}
void AXObject::Detach() {
-#if DCHECK_IS_ON()
- // Only mock objects can end up being detached twice, because their owner
- // may have needed to detach them when they were detached, but couldn't
- // remove them from the object cache yet.
+ // Prevents LastKnown*() methods from returning the wrong values.
+ cached_is_ignored_ = true;
+ cached_is_ignored_but_included_in_tree_ = false;
+
if (IsDetached()) {
+ // Only mock objects can end up being detached twice, because their owner
+ // may have needed to detach them when they were detached, but couldn't
+ // remove them from the object cache yet.
DCHECK(IsMockObject()) << "Object detached twice: " << RoleValue();
return;
}
- DCHECK(!is_adding_children_) << ToString(true, true);
+
+#if DCHECK_IS_ON()
DCHECK(ax_object_cache_);
DCHECK(!ax_object_cache_->IsFrozen())
<< "Do not detach children while the tree is frozen, in order to avoid "
@@ -791,6 +564,15 @@ void AXObject::Detach() {
"accessibility properties.";
#endif
+#if defined(AX_FAIL_FAST_BUILD)
+ SANITIZER_CHECK(!is_adding_children_) << ToString(true, true);
+#endif
+
+ CHECK(!is_loading_inline_boxes_)
+ << "Should not be attempting to detach object while in the middle of "
+ "recursively loading inline text boxes: "
+ << ToString(true, true);
+
// Clear any children and call DetachFromParent() on them so that
// no children are left with dangling pointers to their parent.
ClearChildren();
@@ -804,12 +586,35 @@ bool AXObject::IsDetached() const {
return !ax_object_cache_;
}
-void AXObject::SetParent(AXObject* new_parent) {
- DCHECK(new_parent || IsA<Document>(GetNode()))
- << "Parent cannot be null, except at the root, was null at " << GetNode()
- << " " << GetLayoutObject();
+bool AXObject::IsRoot() const {
+ return GetNode() && GetNode() == &AXObjectCache().GetDocument();
+}
+void AXObject::SetParent(AXObject* new_parent) const {
#if DCHECK_IS_ON()
+ if (!new_parent && !IsRoot()) {
+ std::ostringstream message;
+ message << "Parent cannot be null, except at the root. "
+ "Parent chain from DOM, starting at |this|:";
+ int count = 0;
+ for (Node* node = GetNode(); node;
+ node = GetParentNodeForComputeParent(node)) {
+ message << "\n"
+ << (++count) << ". " << node
+ << "\n LayoutObject=" << node->GetLayoutObject();
+ if (AXObject* obj = AXObjectCache().Get(node))
+ message << "\n " << obj->ToString(true, true);
+ }
+ NOTREACHED() << message.str();
+ }
+
+ if (new_parent) {
+ DCHECK(!new_parent->IsDetached())
+ << "Cannot set parent to a detached object:"
+ << "\n* Child: " << ToString(true, true)
+ << "\n* New parent: " << new_parent->ToString(true, true);
+ }
+
// Check to ensure that if the parent is changing from a previous parent,
// that |this| is not still a child of that one.
// This is similar to the IsParentUnignoredOf() check in
@@ -832,99 +637,177 @@ void AXObject::SetParent(AXObject* new_parent) {
parent_ = new_parent;
}
+bool AXObject::IsMissingParent() const {
+ if (!parent_)
+ return !IsRoot();
+
+ if (parent_->IsDetached())
+ return true;
+
+ return false;
+}
+
+void AXObject::RepairMissingParent() const {
+ DCHECK(IsMissingParent());
+
+ SetParent(ComputeParent());
+}
+
// In many cases, ComputeParent() is not called, because the parent adding
-// the child will pass itself into AXObject::Init() via parent_if_known.
+// the parent adding the child will pass itself into AXObjectCacheImpl.
// ComputeParent() is still necessary because some parts of the code,
// especially web tests, result in AXObjects being created in the middle of
// the tree before their parents are created.
// TODO(accessibility) Consider forcing all ax objects to be created from
// the top down, eliminating the need for ComputeParent().
AXObject* AXObject::ComputeParent() const {
- DCHECK(!IsDetached());
+#if defined(AX_FAIL_FAST_BUILD)
+ SANITIZER_CHECK(!IsDetached());
- DCHECK(!parent_ || parent_->IsDetached())
- << "Should use cached parent unless it's detached, and should not "
- "attempt to recompute it, occurred on "
- << GetNode();
+ SANITIZER_CHECK(!IsVirtualObject())
+ << "A virtual object must have a parent, and cannot exist without one. "
+ "The parent is set when the object is constructed.";
- if (!GetNode() && !GetLayoutObject()) {
- NOTREACHED() << "Can't compute parent on AXObjects without a backing Node "
- "or LayoutObject. Objects without those must set the "
- "parent in Init(), |this| = "
- << RoleValue();
- return nullptr;
- }
+ SANITIZER_CHECK(!IsMockObject())
+ << "A mock object must have a parent, and cannot exist without one. "
+ "The parent is set when the object is constructed.";
- return ComputeParentImpl();
-}
+ SANITIZER_CHECK(GetNode() || GetLayoutObject())
+ << "Can't compute parent on AXObjects without a backing Node "
+ "or LayoutObject. Objects without those must set the "
+ "parent in Init(), |this| = "
+ << RoleValue();
+#endif
-AXObject* AXObject::ComputeParentImpl() const {
- DCHECK(!IsDetached());
+ AXObject* ax_parent =
+ AXObjectCache().IsAriaOwned(this)
+ ? AXObjectCache().GetAriaOwnedParent(this)
+ : ComputeNonARIAParent(AXObjectCache(), GetNode(), GetLayoutObject());
- if (AXObjectCache().IsAriaOwned(this))
- return AXObjectCache().GetAriaOwnedParent(this);
+ CHECK(!ax_parent || !ax_parent->IsDetached())
+ << "Computed parent should never be detached:"
+ << "\n* Child: " << GetNode()
+ << "\n* Parent: " << ax_parent->ToString(true, true);
- Node* current_node = GetNode();
+ return ax_parent;
+}
- // A WebArea's parent should be the page popup owner, if any, otherwise null.
- if (IsA<Document>(current_node)) {
- LocalFrame* frame = GetLayoutObject()->GetFrame();
- return AXObjectCache().GetOrCreate(frame->PagePopupOwner());
+// static
+bool AXObject::CanComputeAsNaturalParent(Node* node) {
+ // A <select> menulist that will use AXMenuList is not allowed.
+ if (AXObjectCacheImpl::UseAXMenuList()) {
+ if (auto* select = DynamicTo<HTMLSelectElement>(node)) {
+ if (select->UsesMenuList())
+ return false;
+ }
}
- if (IsVirtualObject()) {
- NOTREACHED()
- << "A virtual object must have a parent, and cannot exist without one. "
- "The parent is set when the object is constructed.";
- return nullptr;
+ // A <br> can only support AXInlineTextBox children, which is never the result
+ // of a parent computation (the parent of the children is set at Init()).
+ if (IsA<HTMLBRElement>(node))
+ return false;
+
+ // Image map parent-child relationships (from image to area) must be retrieved
+ // manually via AXImageMapLink::GetAXObjectForImageMap().
+ if (IsA<HTMLMapElement>(node) || IsA<HTMLAreaElement>(node) ||
+ IsA<HTMLImageElement>(node)) {
+ return false;
}
- // If no node, or a pseudo element, use the layout parent.
+ return true;
+}
+
+// static
+AXObject* AXObject::ComputeNonARIAParent(AXObjectCacheImpl& cache,
+ Node* current_node,
+ LayoutObject* current_layout_obj) {
+ DCHECK(current_node || current_layout_obj)
+ << "Can't compute parent without a backing Node "
+ "or LayoutObject.";
+
+ // If no node, use the layout parent.
if (!current_node) {
- LayoutObject* current_layout_obj = GetLayoutObject();
- if (!current_layout_obj) {
- NOTREACHED()
- << "Can't compute parent on AXObjects without a backing Node "
- "or LayoutObject. Objects without those must set the "
- "parent in Init(), |this| = "
- << RoleValue();
- return nullptr;
- }
- // If no DOM node and no parent, this must be an anonymous layout object.
+ // If no DOM node, this is an anonymous layout object.
DCHECK(current_layout_obj->IsAnonymous());
+ // In accessibility, this only occurs for descendants of pseudo elements.
+ DCHECK(AXObjectCacheImpl::IsRelevantPseudoElementDescendant(
+ *current_layout_obj))
+ << "Attempt to get AX parent for irrelevant anonymous layout object: "
+ << current_layout_obj;
LayoutObject* parent_layout_obj = current_layout_obj->Parent();
if (!parent_layout_obj)
return nullptr;
- if (AXObject* ax_parent = AXObjectCache().GetOrCreate(parent_layout_obj)) {
- DCHECK(!ax_parent->IsDetached());
- return ax_parent;
- }
- // Switch to using DOM nodes. The only cases that should occur do not have
- // chains of multiple parents without DOM nodes.
Node* parent_node = parent_layout_obj->GetNode();
- DCHECK(parent_node) << "Computing an accessible parent from the layout "
- "parent did not yield an accessible object nor a "
- "DOM node to walk up from, current_layout_obj = "
- << current_layout_obj;
- if (!parent_node)
+ if (!CanComputeAsNaturalParent(parent_node))
return nullptr;
+ if (AXObject* ax_parent = cache.GetOrCreate(parent_layout_obj)) {
+ DCHECK(!ax_parent->IsDetached());
+ DCHECK(ax_parent->ShouldUseLayoutObjectTraversalForChildren())
+ << "Do not compute a parent that cannot have this as a child.";
+ return ax_parent->CanHaveChildren() ? ax_parent : nullptr;
+ }
+ return nullptr;
}
DCHECK(current_node->isConnected())
<< "Should not call ComputeParent() with disconnected node: "
<< current_node;
- while (true) {
- current_node = GetParentNodeForComputeParent(current_node);
- if (!current_node)
- break;
- AXObject* ax_parent = AXObjectCache().GetOrCreate(current_node);
- if (ax_parent) {
- DCHECK(!ax_parent->IsDetached());
- return ax_parent;
+ // A WebArea's parent should be the page popup owner, if any, otherwise null.
+ if (auto* document = DynamicTo<Document>(current_node)) {
+ LocalFrame* frame = document->GetFrame();
+ DCHECK(frame);
+ return cache.GetOrCreate(frame->PagePopupOwner());
+ }
+
+ // For <option> in <select size=1>, return the popup.
+ if (AXObjectCacheImpl::UseAXMenuList()) {
+ if (auto* option = DynamicTo<HTMLOptionElement>(current_node)) {
+ if (AXObject* ax_select =
+ AXMenuListOption::ComputeParentAXMenuPopupFor(cache, option)) {
+ return ax_select;
+ }
+ }
+ }
+
+ // For <area>, return the image it is a child link of.
+ if (IsA<HTMLAreaElement>(current_node)) {
+ if (AXObject* ax_image =
+ AXImageMapLink::GetAXObjectForImageMap(cache, current_node)) {
+ return ax_image;
}
}
+ Node* parent_node = GetParentNodeForComputeParent(current_node);
+ if (!parent_node) {
+ // This occurs when a DOM child isn't visited by LayoutTreeBuilderTraversal,
+ // such as an element child of a <textarea>, which only supports plain text.
+ return nullptr;
+ }
+
+ // When the flag to use AXMenuList in on, a menu list is only allowed to
+ // parent an AXMenuListPopup, which is added as a child on creation. No other
+ // children are allowed, and nullptr is returned for anything else where the
+ // parent would be AXMenuList.
+ if (AXObjectCacheImpl::ShouldCreateAXMenuListFor(
+ parent_node->GetLayoutObject())) {
+ return nullptr;
+ }
+
+ if (!CanComputeAsNaturalParent(parent_node))
+ return nullptr;
+
+ if (AXObject* ax_parent = cache.GetOrCreate(parent_node)) {
+ DCHECK(!ax_parent->IsDetached());
+ // If the parent can't have children, then return null so that the caller
+ // knows that it is not a relevant natural parent, as it is a leaf.
+ return ax_parent->CanHaveChildren() ? ax_parent : nullptr;
+ }
+
+ // Could not create AXObject for |parent_node|, therefore there is no relevant
+ // natural parent. For example, the AXObject that would have been created
+ // would have been a descendant of a leaf, or otherwise an illegal child of a
+ // specialized object.
return nullptr;
}
@@ -935,6 +818,8 @@ void AXObject::EnsureCorrectParentComputation() {
DCHECK(!parent_->IsDetached());
+ DCHECK(parent_->CanHaveChildren());
+
// Don't check the computed parent if the cached parent is a mock object.
// It is expected that a computed parent could never be a mock object,
// which has no backing DOM node or layout object, and therefore cannot be
@@ -958,24 +843,28 @@ void AXObject::EnsureCorrectParentComputation() {
if (GetNode() && GetNode()->IsPseudoElement())
return;
- // Verify that the algorithm in ComputeParentImpl() provides same results as
- // parents that init their children with themselves as the parent_if_known.
- // Inconsistency indicates a problem could potentially exist where a child's
- // parent does not include the child in its children.
- DCHECK(ComputeParentImpl())
- << "Computed parent was null for " << this << ", expected " << parent_;
- DCHECK_EQ(ComputeParentImpl(), parent_)
+ // Verify that the algorithm in ComputeParent() provides same results as
+ // parents that init their children with themselves as the parent.
+ // Inconsistency indicates a problem could potentially exist where a child's
+ // parent does not include the child in its children.
+#if DCHECK_IS_ON()
+ AXObject* computed_parent = ComputeParent();
+
+ DCHECK(computed_parent) << "Computed parent was null for " << this
+ << ", expected " << parent_;
+ DCHECK_EQ(computed_parent, parent_)
<< "\n**** ComputeParent should have provided the same result as "
"the known parent.\n**** Computed parent layout object was "
- << ComputeParentImpl()->GetLayoutObject()
+ << computed_parent->GetLayoutObject()
<< "\n**** Actual parent's layout object was "
<< parent_->GetLayoutObject() << "\n**** Child was " << this;
+#endif
}
#endif
const AtomicString& AXObject::GetAOMPropertyOrARIAAttribute(
AOMStringProperty property) const {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return g_null_atom;
@@ -984,7 +873,7 @@ const AtomicString& AXObject::GetAOMPropertyOrARIAAttribute(
Element* AXObject::GetAOMPropertyOrARIAAttribute(
AOMRelationProperty property) const {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return nullptr;
@@ -993,7 +882,7 @@ Element* AXObject::GetAOMPropertyOrARIAAttribute(
bool AXObject::HasAOMProperty(AOMRelationListProperty property,
HeapVector<Member<Element>>& result) const {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return false;
@@ -1003,7 +892,7 @@ bool AXObject::HasAOMProperty(AOMRelationListProperty property,
bool AXObject::HasAOMPropertyOrARIAAttribute(
AOMRelationListProperty property,
HeapVector<Member<Element>>& result) const {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return false;
@@ -1012,7 +901,7 @@ bool AXObject::HasAOMPropertyOrARIAAttribute(
bool AXObject::HasAOMPropertyOrARIAAttribute(AOMBooleanProperty property,
bool& result) const {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return false;
@@ -1040,7 +929,7 @@ bool AXObject::AOMPropertyOrARIAAttributeIsFalse(
bool AXObject::HasAOMPropertyOrARIAAttribute(AOMUIntProperty property,
uint32_t& result) const {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return false;
@@ -1052,7 +941,7 @@ bool AXObject::HasAOMPropertyOrARIAAttribute(AOMUIntProperty property,
bool AXObject::HasAOMPropertyOrARIAAttribute(AOMIntProperty property,
int32_t& result) const {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return false;
@@ -1064,7 +953,7 @@ bool AXObject::HasAOMPropertyOrARIAAttribute(AOMIntProperty property,
bool AXObject::HasAOMPropertyOrARIAAttribute(AOMFloatProperty property,
float& result) const {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return false;
@@ -1076,7 +965,7 @@ bool AXObject::HasAOMPropertyOrARIAAttribute(AOMFloatProperty property,
bool AXObject::HasAOMPropertyOrARIAAttribute(AOMStringProperty property,
AtomicString& result) const {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return false;
@@ -1118,7 +1007,7 @@ void AXObject::Serialize(ui::AXNodeData* node_data,
node_data->AddState(ax::mojom::blink::State::kEditable);
if (IsEditableRoot()) {
node_data->AddBoolAttribute(
- ax::mojom::blink::BoolAttribute::kEditableRoot, true);
+ ax::mojom::blink::BoolAttribute::kContentEditableRoot, true);
}
if (IsRichlyEditable())
node_data->AddState(ax::mojom::blink::State::kRichlyEditable);
@@ -1227,7 +1116,7 @@ void AXObject::SerializeUnignoredAttributes(ui::AXNodeData* node_data,
if (auto* html_frame_owner_element =
DynamicTo<HTMLFrameOwnerElement>(GetElement())) {
if (Frame* child_frame = html_frame_owner_element->ContentFrame()) {
- base::Optional<base::UnguessableToken> child_token =
+ absl::optional<base::UnguessableToken> child_token =
child_frame->GetEmbeddingToken();
if (child_token && !(IsDetached() || ChildCountIncludingIgnored())) {
ui::AXTreeID child_tree_id =
@@ -1259,6 +1148,14 @@ void AXObject::SerializeUnignoredAttributes(ui::AXNodeData* node_data,
SerializeTableAttributes(node_data);
}
+ if (accessibility_mode.has_mode(ui::AXMode::kScreenReader)) {
+ // Whether it has ARIA attributes at all.
+ if (HasAriaAttribute()) {
+ node_data->AddBoolAttribute(
+ ax::mojom::blink::BoolAttribute::kHasAriaAttribute, true);
+ }
+ }
+
if (accessibility_mode.has_mode(ui::AXMode::kPDF)) {
// Return early. None of the following attributes are needed for PDFs.
return;
@@ -1295,21 +1192,7 @@ void AXObject::SerializeUnignoredAttributes(ui::AXNodeData* node_data,
SerializeSparseAttributes(node_data);
if (Element* element = GetElement()) {
- if (const AtomicString& aria_role =
- GetAOMPropertyOrARIAAttribute(AOMStringProperty::kRole)) {
- TruncateAndAddStringAttribute(node_data,
- ax::mojom::blink::StringAttribute::kRole,
- aria_role.Utf8());
- } else {
- std::string role_str = GetEquivalentAriaRoleString(RoleValue());
- if (!role_str.empty()) {
- TruncateAndAddStringAttribute(node_data,
- ax::mojom::blink::StringAttribute::kRole,
- GetEquivalentAriaRoleString(RoleValue()));
- }
- }
-
- if (IsNativeTextField()) {
+ if (IsAtomicTextField()) {
// Selection offsets are only used for plain text controls, (input of a
// text field type, and textarea). Rich editable areas, such as
// contenteditables, use AXTreeData.
@@ -1424,7 +1307,7 @@ void AXObject::SerializeScrollAttributes(ui::AXNodeData* node_data) {
}
void AXObject::SerializeElementAttributes(ui::AXNodeData* node_data) {
- Element* element = this->GetElement();
+ Element* element = GetElement();
if (!element)
return;
@@ -1440,10 +1323,11 @@ void AXObject::SerializeElementAttributes(ui::AXNodeData* node_data) {
TruncateAndAddStringAttribute(
node_data, ax::mojom::blink::StringAttribute::kRole, aria_role.Utf8());
} else {
- std::string role = GetEquivalentAriaRoleString(RoleValue());
- if (!role.empty()) {
- TruncateAndAddStringAttribute(
- node_data, ax::mojom::blink::StringAttribute::kRole, role);
+ const AtomicString& role_str = GetEquivalentAriaRoleName(RoleValue());
+ if (role_str != g_null_atom) {
+ TruncateAndAddStringAttribute(node_data,
+ ax::mojom::blink::StringAttribute::kRole,
+ role_str.Ascii());
}
}
}
@@ -1740,7 +1624,7 @@ bool AXObject::IsAnchor() const {
}
bool AXObject::IsARIATextField() const {
- if (IsNativeTextField())
+ if (IsAtomicTextField())
return false; // Native role supercedes the ARIA one.
return AriaRoleAttribute() == ax::mojom::blink::Role::kTextField ||
AriaRoleAttribute() == ax::mojom::blink::Role::kSearchBox ||
@@ -1755,19 +1639,6 @@ bool AXObject::IsCanvas() const {
return RoleValue() == ax::mojom::blink::Role::kCanvas;
}
-bool AXObject::IsCheckboxOrRadio() const {
- switch (RoleValue()) {
- case ax::mojom::blink::Role::kCheckBox:
- case ax::mojom::blink::Role::kMenuItemCheckBox:
- case ax::mojom::blink::Role::kMenuItemRadio:
- case ax::mojom::blink::Role::kRadioButton:
- return true;
- default:
- break;
- }
- return false;
-}
-
bool AXObject::IsColorWell() const {
return RoleValue() == ax::mojom::blink::Role::kColorWell;
}
@@ -1863,7 +1734,7 @@ ax::mojom::blink::CheckedState AXObject::CheckedState() const {
// Native checked state
if (role != ax::mojom::blink::Role::kToggleButton) {
- const Node* node = this->GetNode();
+ const Node* node = GetNode();
if (!node)
return ax::mojom::blink::CheckedState::kNone;
@@ -1957,15 +1828,15 @@ bool AXObject::IsNativeSpinButton() const {
return false;
}
-bool AXObject::IsNativeTextField() const {
+bool AXObject::IsAtomicTextField() const {
return blink::IsTextControl(GetNode());
}
-bool AXObject::IsNonNativeTextField() const {
+bool AXObject::IsNonAtomicTextField() const {
// Consivably, an <input type=text> or a <textarea> might also have the
// contenteditable attribute applied. In such cases, the <input> or <textarea>
// tags should supercede.
- if (IsNativeTextField())
+ if (IsAtomicTextField())
return false;
return HasContentEditableAttributeSet() || IsARIATextField();
}
@@ -2027,7 +1898,7 @@ bool AXObject::IsTabItem() const {
bool AXObject::IsTextField() const {
if (IsDetached())
return false;
- return IsNativeTextField() || IsNonNativeTextField();
+ return IsAtomicTextField() || IsNonAtomicTextField();
}
bool AXObject::IsAutofillAvailable() const {
@@ -2140,12 +2011,10 @@ void AXObject::UpdateCachedAttributeValuesIfNeeded(
DocumentLifecycle::kAfterPerformLayout)
<< "Unclean document at lifecycle "
<< GetDocument()->Lifecycle().ToString();
-#endif
+#endif // DCHECK_IS_ON()
- // TODO(accessibility) Every AXObject must have a parent except the root.
- // Sometimes the parent is detached and a new parent isn't yet reattached.
- if (!parent_)
- parent_ = ComputeParent();
+ if (IsMissingParent())
+ RepairMissingParent();
cached_is_hidden_via_style = ComputeIsHiddenViaStyle();
@@ -2160,10 +2029,7 @@ void AXObject::UpdateCachedAttributeValuesIfNeeded(
SetNeedsToUpdateChildren();
cached_is_inert_or_aria_hidden_ = is_inert_or_aria_hidden;
}
- cached_is_descendant_of_leaf_node_ = !!LeafNodeAncestor();
cached_is_descendant_of_disabled_node_ = !!DisabledAncestor();
- cached_has_inherited_presentational_role_ =
- !!InheritsPresentationalRoleFrom();
bool is_ignored = ComputeAccessibilityIsIgnored();
bool is_ignored_but_included_in_tree =
@@ -2178,7 +2044,8 @@ void AXObject::UpdateCachedAttributeValuesIfNeeded(
DisplayLockUtilities::NearestLockedExclusiveAncestor(*GetNode())) {
DCHECK(!cached_is_ignored_but_included_in_tree_)
<< "Display locked text should not be included in the tree (subject to "
- "future rule change)";
+ "future rule change): "
+ << ToString(true, true);
}
#endif
bool included_in_tree_changed = false;
@@ -2207,7 +2074,6 @@ void AXObject::UpdateCachedAttributeValuesIfNeeded(
cached_is_ignored_ = is_ignored;
cached_is_ignored_but_included_in_tree_ = is_ignored_but_included_in_tree;
- cached_is_editable_root_ = ComputeIsEditableRoot();
// Compute live region root, which can be from any ARIA live value, including
// "off", or from an automatic ARIA live value, e.g. from role="status".
// TODO(dmazzoni): remove this const_cast.
@@ -2236,9 +2102,10 @@ void AXObject::UpdateCachedAttributeValuesIfNeeded(
// "not handle a signal and call ChilldrenChanged() earlier."
// << "\nChild: " << ToString(true)
// << "\nParent: " << parent->ToString(true);
- // Defer this ChildrenChanged(), otherwise it can cause reentry into
+ // Defers a ChildrenChanged() on the first included ancestor.
+ // Must defer it, otherwise it can cause reentry into
// UpdateCachedAttributeValuesIfNeeded() on |this|.
- AXObjectCache().ChildrenChanged(parent);
+ AXObjectCache().ChildrenChangedOnAncestorOf(const_cast<AXObject*>(this));
}
}
@@ -2371,22 +2238,6 @@ bool AXObject::IsVisible() const {
return !IsInertOrAriaHidden() && !IsHiddenViaStyle();
}
-bool AXObject::IsDescendantOfLeafNode() const {
- UpdateCachedAttributeValuesIfNeeded();
- return cached_is_descendant_of_leaf_node_;
-}
-
-AXObject* AXObject::LeafNodeAncestor() const {
- if (AXObject* parent = ParentObject()) {
- if (!parent->CanHaveChildren())
- return parent;
-
- return parent->LeafNodeAncestor();
- }
-
- return nullptr;
-}
-
const AXObject* AXObject::AriaHiddenRoot() const {
for (const AXObject* object = this; object; object = object->ParentObject()) {
if (object->AOMPropertyOrARIAAttributeIsTrue(AOMBooleanProperty::kHidden))
@@ -2554,28 +2405,81 @@ bool AXObject::ComputeAccessibilityIsIgnoredButIncludedInTree() const {
return true;
}
- // Include all pseudo element content. Any anonymous subtree is included
- // from above, in the condition where there is no node.
- if (node->IsPseudoElement())
+ // 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) &&
+ IsHiddenViaStyle()) {
+ return true;
+ }
+ }
+ } else if (GetLayoutObject()) {
+ if (GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible)
+ 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() && IsAriaHidden())
return true;
+ // Custom elements and their children are included in the tree.
// <slot>s and their children are included in the tree.
- // TODO(accessibility) Consider including all shadow content; however, this
- // can actually be a lot of nodes inside of a web component, e.g. svg.
- if (IsA<HTMLSlotElement>(node))
+ // This checks to see if this a child one of those.
+ if (Node* parent_node = LayoutTreeBuilderTraversal::Parent(*node)) {
+ if (parent_node->IsCustomElement() || IsA<HTMLSlotElement>(parent_node))
+ return true;
+ }
+
+ Element* element = GetElement();
+ if (!element)
+ return false;
+
+ // Custom elements and their children are included in the tree.
+ if (element->IsCustomElement())
+ return true;
+
+ // <slot>s and their children are included in the tree.
+ // Detailed explanation:
+ // <slot> elements are placeholders marking locations in a shadow tree where
+ // users of a web component can insert their own custom nodes. Inserted nodes
+ // (also known as distributed nodes) become children of their respective slots
+ // in the accessibility tree. In other words, the accessibility tree mirrors
+ // the flattened DOM tree or the layout tree, not the original DOM tree.
+ // Distributed nodes still maintain their parent relations and computed style
+ // information with their original location in the DOM. Therefore, we need to
+ // ensure that in the accessibility tree no remnant information from the
+ // unflattened DOM tree remains, such as the cached parent.
+ if (IsA<HTMLSlotElement>(element))
return true;
- if (CachedParentObject() &&
- IsA<HTMLSlotElement>(CachedParentObject()->GetNode()))
+
+ // Include all pseudo element content. Any anonymous subtree is included
+ // from above, in the condition where there is no node.
+ if (element->IsPseudoElement())
return true;
- if (GetElement() && GetElement()->IsCustomElement())
+ // Include all parents of ::before/::after/::marker pseudo elements to help
+ // ClearChildren() find all children, and assist naming computation.
+ // It is unnecessary to include a rule for other types of pseudo elements:
+ // Specifically, ::first-letter/::backdrop are not visited by
+ // LayoutTreeBuilderTraversal, and cannot be in the tree, therefore do not add
+ // a special rule to include their parents.
+ if (element->GetPseudoElement(kPseudoIdBefore) ||
+ element->GetPseudoElement(kPseudoIdAfter) ||
+ element->GetPseudoElement(kPseudoIdMarker)) {
return true;
+ }
// Use a flag to control whether or not the <html> element is included
// in the accessibility tree. Either way it's always marked as "ignored",
// but eventually we want to always include it in the tree to simplify
// some logic.
- if (IsA<HTMLHtmlElement>(node))
+ if (IsA<HTMLHtmlElement>(element))
return RuntimeEnabledFeatures::AccessibilityExposeHTMLElementEnabled();
// Keep the internal accessibility tree consistent for videos which lack
@@ -2590,41 +2494,24 @@ bool AXObject::ComputeAccessibilityIsIgnoredButIncludedInTree() const {
if (IsLineBreakingObject())
return true;
- // 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) &&
- IsHiddenViaStyle()) {
- return true;
- }
- }
- } else if (GetLayoutObject()) {
- if (GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible)
- 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() && IsAriaHidden())
- return true;
-
// Preserve SVG grouping elements.
- if (IsA<SVGGElement>(node))
+ if (IsA<SVGGElement>(element))
return true;
// Keep table-related elements in the tree, because it's too easy for them
// to in and out of being ignored based on their ancestry, as their role
// can depend on several levels up in the hierarchy.
- if (IsA<HTMLTableElement>(node) || IsA<HTMLTableSectionElement>(node) ||
- IsA<HTMLTableRowElement>(node) || IsA<HTMLTableCellElement>(node)) {
+ if (IsA<HTMLTableElement>(element) || IsA<HTMLTableSectionElement>(element) ||
+ IsA<HTMLTableRowElement>(element) || IsA<HTMLTableCellElement>(element)) {
return true;
}
+ // Ensure clean teardown of AXMenuList.
+ if (auto* option = DynamicTo<HTMLOptionElement>(element)) {
+ if (option->OwnerSelectElement())
+ return true;
+ }
+
// Preserve nodes with language attributes.
if (HasAttribute(html_names::kLangAttr))
return true;
@@ -2632,16 +2519,16 @@ bool AXObject::ComputeAccessibilityIsIgnoredButIncludedInTree() const {
return false;
}
-const AXObject* AXObject::GetNativeTextControlAncestor(
+const AXObject* AXObject::GetAtomicTextFieldAncestor(
int max_levels_to_check) const {
- if (IsNativeTextField())
+ if (IsAtomicTextField())
return this;
if (max_levels_to_check == 0)
return nullptr;
if (AXObject* parent = ParentObject())
- return parent->GetNativeTextControlAncestor(max_levels_to_check - 1);
+ return parent->GetAtomicTextFieldAncestor(max_levels_to_check - 1);
return nullptr;
}
@@ -2667,10 +2554,15 @@ const AXObject* AXObject::DatetimeAncestor(int max_levels_to_check) const {
}
bool AXObject::LastKnownIsIgnoredValue() const {
+ DCHECK(cached_is_ignored_ || !IsDetached())
+ << "A detached object should always indicate that it is ignored so that "
+ "it won't ever accidentally be included in the tree.";
return cached_is_ignored_;
}
bool AXObject::LastKnownIsIgnoredButIncludedInTreeValue() const {
+ DCHECK(!cached_is_ignored_but_included_in_tree_ || !IsDetached())
+ << "A detached object should never be included in the tree.";
return cached_is_ignored_but_included_in_tree_;
}
@@ -2685,11 +2577,6 @@ ax::mojom::blink::Role AXObject::DetermineAccessibilityRole() {
return NativeRoleIgnoringAria();
}
-bool AXObject::HasInheritedPresentationalRole() const {
- UpdateCachedAttributeValuesIfNeeded();
- return cached_has_inherited_presentational_role_;
-}
-
bool AXObject::CanSetValueAttribute() const {
switch (RoleValue()) {
case ax::mojom::blink::Role::kColorWell:
@@ -3163,7 +3050,7 @@ String AXObject::AriaTextAlternative(bool recursive,
if (element) {
HeapVector<Member<Element>> elements_from_attribute;
Vector<String> ids;
- ElementsFromAttribute(elements_from_attribute, attr, ids);
+ ElementsFromAttribute(element, elements_from_attribute, attr, ids);
const AtomicString& aria_labelledby = GetAttribute(attr);
@@ -3257,13 +3144,14 @@ String AXObject::TextFromElements(
return accumulated_text.ToString();
}
-void AXObject::TokenVectorFromAttribute(Vector<String>& tokens,
- const QualifiedName& attribute) const {
- Node* node = this->GetNode();
- if (!node || !node->IsElementNode())
+// static
+void AXObject::TokenVectorFromAttribute(Element* element,
+ Vector<String>& tokens,
+ const QualifiedName& attribute) {
+ if (!element)
return;
- String attribute_value = GetAttribute(attribute).GetString();
+ String attribute_value = element->FastGetAttribute(attribute).GetString();
if (attribute_value.IsEmpty())
return;
@@ -3271,47 +3159,84 @@ void AXObject::TokenVectorFromAttribute(Vector<String>& tokens,
attribute_value.Split(' ', tokens);
}
-void AXObject::ElementsFromAttribute(HeapVector<Member<Element>>& elements,
+// static
+bool AXObject::ElementsFromAttribute(Element* from,
+ HeapVector<Member<Element>>& elements,
const QualifiedName& attribute,
- Vector<String>& ids) const {
+ Vector<String>& ids) {
+ if (!from)
+ return false;
+
// We compute the attr-associated elements, which are either explicitly set
// element references set via the IDL, or computed from the content attribute.
- TokenVectorFromAttribute(ids, attribute);
- Element* element = GetElement();
- if (!element)
- return;
+ TokenVectorFromAttribute(from, ids, attribute);
- base::Optional<HeapVector<Member<Element>>> attr_associated_elements =
- element->GetElementArrayAttribute(attribute);
+ absl::optional<HeapVector<Member<Element>>> attr_associated_elements =
+ from->GetElementArrayAttribute(attribute);
if (!attr_associated_elements)
- return;
+ return false;
for (const auto& element : attr_associated_elements.value())
elements.push_back(element);
+
+ return elements.size();
}
-void AXObject::AriaLabelledbyElementVector(
+// static
+bool AXObject::AriaLabelledbyElementVector(
+ Element* from,
HeapVector<Member<Element>>& elements,
- Vector<String>& ids) const {
+ Vector<String>& ids) {
// Try both spellings, but prefer aria-labelledby, which is the official spec.
- ElementsFromAttribute(elements, html_names::kAriaLabelledbyAttr, ids);
- if (!ids.size())
- ElementsFromAttribute(elements, html_names::kAriaLabeledbyAttr, ids);
+ if (ElementsFromAttribute(from, elements, html_names::kAriaLabelledbyAttr,
+ ids)) {
+ return true;
+ }
+
+ return ElementsFromAttribute(from, elements, html_names::kAriaLabeledbyAttr,
+ ids);
+}
+
+// static
+bool AXObject::IsNameFromAriaAttribute(Element* element) {
+ // TODO(accessibility) Make this work for virtual nodes.
+
+ if (!element)
+ return false;
+
+ HeapVector<Member<Element>> elements_from_attribute;
+ Vector<String> ids;
+ if (AriaLabelledbyElementVector(element, elements_from_attribute, ids))
+ return true;
+
+ const AtomicString& aria_label = AccessibleNode::GetPropertyOrARIAAttribute(
+ element, AOMStringProperty::kLabel);
+ if (!aria_label.IsEmpty())
+ return true;
+
+ return false;
+}
+
+bool AXObject::IsNameFromAuthorAttribute() const {
+ return IsNameFromAriaAttribute(GetElement()) ||
+ HasAttribute(html_names::kTitleAttr);
}
String AXObject::TextFromAriaLabelledby(AXObjectSet& visited,
AXRelatedObjectVector* related_objects,
Vector<String>& ids) const {
HeapVector<Member<Element>> elements;
- AriaLabelledbyElementVector(elements, ids);
+ AriaLabelledbyElementVector(GetElement(), elements, ids);
return TextFromElements(true, visited, elements, related_objects);
}
String AXObject::TextFromAriaDescribedby(AXRelatedObjectVector* related_objects,
Vector<String>& ids) const {
AXObjectSet visited;
+
HeapVector<Member<Element>> elements;
- ElementsFromAttribute(elements, html_names::kAriaDescribedbyAttr, ids);
+ ElementsFromAttribute(GetElement(), elements,
+ html_names::kAriaDescribedbyAttr, ids);
return TextFromElements(true, visited, elements, related_objects);
}
@@ -3321,7 +3246,12 @@ AccessibilityOrientation AXObject::Orientation() const {
return kAccessibilityOrientationUndefined;
}
-void AXObject::LoadInlineTextBoxes() {}
+void AXObject::LoadInlineTextBoxes() {
+ base::AutoReset<bool> reentrancy_protector(&is_loading_inline_boxes_, true);
+ LoadInlineTextBoxesRecursive();
+}
+
+void AXObject::LoadInlineTextBoxesRecursive() {}
AXObject* AXObject::NextOnLine() const {
return nullptr;
@@ -3331,7 +3261,7 @@ AXObject* AXObject::PreviousOnLine() const {
return nullptr;
}
-base::Optional<const DocumentMarker::MarkerType>
+absl::optional<const DocumentMarker::MarkerType>
AXObject::GetAriaSpellingOrGrammarMarker() const {
AtomicString aria_invalid_value;
const AncestorsIterator iter = std::find_if(
@@ -3343,12 +3273,12 @@ AXObject::GetAriaSpellingOrGrammarMarker() const {
});
if (iter == UnignoredAncestorsEnd())
- return base::nullopt;
+ return absl::nullopt;
if (EqualIgnoringASCIICase(aria_invalid_value, "spelling"))
return DocumentMarker::kSpelling;
if (EqualIgnoringASCIICase(aria_invalid_value, "grammar"))
return DocumentMarker::kGrammar;
- return base::nullopt;
+ return absl::nullopt;
}
void AXObject::TextCharacterOffsets(Vector<int>&) const {}
@@ -3357,7 +3287,7 @@ void AXObject::GetWordBoundaries(Vector<int>& word_starts,
Vector<int>& word_ends) const {}
int AXObject::TextLength() const {
- if (IsNativeTextField())
+ if (IsAtomicTextField())
return GetValueForControl().length();
return 0;
}
@@ -3461,76 +3391,60 @@ bool AXObject::SupportsARIAExpanded() const {
}
}
-bool IsGlobalARIAAttribute(const AtomicString& name) {
- if (!name.StartsWith("ARIA"))
- return false;
- if (name.StartsWith("ARIA-ATOMIC"))
- return true;
- if (name.StartsWith("ARIA-BUSY"))
- return true;
- if (name.StartsWith("ARIA-CONTROLS"))
- return true;
- if (name.StartsWith("ARIA-CURRENT"))
- return true;
- if (name.StartsWith("ARIA-DESCRIBEDBY"))
- return true;
- if (name.StartsWith("ARIA-DETAILS"))
- return true;
- if (name.StartsWith("ARIA-DISABLED"))
- return true;
- if (name.StartsWith("ARIA-DROPEFFECT"))
- return true;
- if (name.StartsWith("ARIA-ERRORMESSAGE"))
- return true;
- if (name.StartsWith("ARIA-FLOWTO"))
- return true;
- if (name.StartsWith("ARIA-GRABBED"))
- return true;
- if (name.StartsWith("ARIA-HASPOPUP"))
- return true;
- if (name.StartsWith("ARIA-HIDDEN"))
- return true;
- if (name.StartsWith("ARIA-INVALID"))
- return true;
- if (name.StartsWith("ARIA-KEYSHORTCUTS"))
- return true;
- if (name.StartsWith("ARIA-LABEL"))
- return true;
- if (name.StartsWith("ARIA-LABELEDBY"))
- return true;
- if (name.StartsWith("ARIA-LABELLEDBY"))
- return true;
- if (name.StartsWith("ARIA-LIVE"))
- return true;
- if (name.StartsWith("ARIA-OWNS"))
- return true;
- if (name.StartsWith("ARIA-RELEVANT"))
- return true;
- if (name.StartsWith("ARIA-ROLEDESCRIPTION"))
- return true;
- return false;
-}
-
-bool AXObject::HasGlobalARIAAttribute() const {
+bool DoesUndoRolePresentation(const AtomicString& name) {
+ // This is the list of global ARIA properties that force
+ // role="presentation"/"none" to be exposed, and does not contain ARIA
+ // properties who's global status is being deprecated.
+ // clang-format off
+ DEFINE_STATIC_LOCAL(
+ HashSet<AtomicString>, aria_global_properties,
+ ({
+ "ARIA-ATOMIC",
+ // TODO(accessibility/ARIA 1.3) Add (and test in aria-global.html)
+ // "ARIA-BRAILLEROLEDESCRIPTION",
+ "ARIA-BUSY",
+ "ARIA-CONTROLS",
+ "ARIA-CURRENT",
+ "ARIA-DESCRIBEDBY",
+ "ARIA-DESCRIPTION",
+ "ARIA-DETAILS",
+ "ARIA-DROPEFFECT",
+ "ARIA-FLOWTO",
+ "ARIA-GRABBED",
+ "ARIA-HIDDEN", // For aria-hidden=false.
+ "ARIA-KEYSHORTCUTS",
+ "ARIA-LIVE",
+ "ARIA-OWNS",
+ "ARIA-RELEVANT",
+ "ARIA-ROLEDESCRIPTION"
+ }));
+ // clang-format on
+
+ return aria_global_properties.Contains(name);
+}
+
+bool AXObject::HasAriaAttribute(bool does_undo_role_presentation) const {
auto* element = GetElement();
if (!element)
return false;
+ // A role is considered an ARIA attribute.
+ if (!does_undo_role_presentation &&
+ AriaRoleAttribute() != ax::mojom::blink::Role::kUnknown) {
+ return true;
+ }
+
+ // Check for any attribute that begins with "aria-".
AttributeCollection attributes = element->AttributesWithoutUpdate();
for (const Attribute& attr : attributes) {
// Attributes cache their uppercase names.
auto name = attr.GetName().LocalNameUpper();
- if (IsGlobalARIAAttribute(name))
- return true;
- }
- if (!element->DidAttachInternals())
- return false;
- const auto& internals_attributes =
- element->EnsureElementInternals().GetAttributes();
- for (const QualifiedName& attr : internals_attributes.Keys()) {
- if (IsGlobalARIAAttribute(attr.LocalNameUpper()))
- return true;
+ if (name.StartsWith("ARIA-")) {
+ if (!does_undo_role_presentation || DoesUndoRolePresentation(name))
+ return true;
+ }
}
+
return false;
}
@@ -3637,13 +3551,28 @@ ax::mojom::blink::Role AXObject::AriaRoleAttribute() const {
return ax::mojom::blink::Role::kUnknown;
}
-ax::mojom::blink::Role AXObject::DetermineAriaRoleAttribute() const {
+ax::mojom::blink::Role AXObject::RawAriaRole() const {
const AtomicString& aria_role =
GetAOMPropertyOrARIAAttribute(AOMStringProperty::kRole);
if (aria_role.IsNull() || aria_role.IsEmpty())
return ax::mojom::blink::Role::kUnknown;
+ return AriaRoleStringToRoleEnum(aria_role);
+}
- ax::mojom::blink::Role role = AriaRoleToWebCoreRole(aria_role);
+ax::mojom::blink::Role AXObject::DetermineAriaRoleAttribute() const {
+ ax::mojom::blink::Role role = RawAriaRole();
+
+ if (role == ax::mojom::blink::Role::kRegion && !IsNameFromAuthorAttribute() &&
+ !HasAttribute(html_names::kAriaRoledescriptionAttr)) {
+ // Nameless ARIA regions fall back on the native element's role.
+ // We only check aria-label/aria-labelledby because those are the only
+ // allowed ways to name an ARIA region.
+ // TODO(accessibility) The aria-roledescription logic is required, otherwise
+ // ChromeVox will ignore the aria-roledescription. It only speaks the role
+ // description on certain roles, and ignores it on the generic role.
+ // See also https://github.com/w3c/aria/issues/1463.
+ return ax::mojom::blink::Role::kUnknown;
+ }
// ARIA states if an item can get focus, it should not be presentational.
// It also states user agents should ignore the presentational role if
@@ -3652,9 +3581,13 @@ ax::mojom::blink::Role AXObject::DetermineAriaRoleAttribute() const {
if (IsA<HTMLIFrameElement>(*GetNode()) || IsA<HTMLFrameElement>(*GetNode()))
return ax::mojom::blink::Role::kIframePresentational;
if ((GetElement() && GetElement()->SupportsFocus()) ||
- HasGlobalARIAAttribute()) {
- // If we return an unknown role, then the native HTML role would be used
- // instead.
+ HasAriaAttribute(true /* does_undo_role_presentation */)) {
+ // Must be exposed with a role if focusable or has a global ARIA property
+ // that is allowed in this context. See
+ // https://w3c.github.io/aria/#presentation for more information about the
+ // conditions upon which elements with role="none"/"presentation" must be
+ // included in the tree. Return Role::kUnknown, so that the native HTML
+ // role is used instead.
return ax::mojom::blink::Role::kUnknown;
}
}
@@ -3671,7 +3604,7 @@ ax::mojom::blink::Role AXObject::DetermineAriaRoleAttribute() const {
// ax::mojom::blink::Role::kComboBoxMenuButton:
// <div tabindex=0 role="combobox">Select</div>
if (role == ax::mojom::blink::Role::kComboBoxGrouping) {
- if (IsNativeTextField())
+ if (IsAtomicTextField())
role = ax::mojom::blink::Role::kTextFieldWithComboBox;
else if (GetElement() && GetElement()->SupportsFocus())
role = ax::mojom::blink::Role::kComboBoxMenuButton;
@@ -3680,9 +3613,71 @@ ax::mojom::blink::Role AXObject::DetermineAriaRoleAttribute() const {
return role;
}
+ax::mojom::blink::HasPopup AXObject::HasPopup() const {
+ return ax::mojom::blink::HasPopup::kFalse;
+}
+
+bool AXObject::IsEditable() const {
+ const Node* node = GetNode();
+ if (IsDetached() || !node)
+ return false;
+#if DCHECK_IS_ON() // Required in order to get Lifecycle().ToString()
+ DCHECK(GetDocument());
+ DCHECK_GE(GetDocument()->Lifecycle().GetState(),
+ DocumentLifecycle::kStyleClean)
+ << "Unclean document style at lifecycle state "
+ << GetDocument()->Lifecycle().ToString();
+#endif // DCHECK_IS_ON()
+
+ if (HasEditableStyle(*node))
+ return true;
+
+ // For the purposes of accessibility, atomic text fields i.e. input and
+ // textarea are editable because the user can potentially enter text in them.
+ if (IsAtomicTextField())
+ return true;
+
+ return false;
+}
+
bool AXObject::IsEditableRoot() const {
- UpdateCachedAttributeValuesIfNeeded();
- return cached_is_editable_root_;
+ return false;
+}
+
+bool AXObject::HasContentEditableAttributeSet() const {
+ return false;
+}
+
+bool AXObject::IsMultiline() const {
+ if (IsDetached() || !GetNode() || !IsTextField())
+ return false;
+
+ bool is_multiline = false;
+ if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kMultiline,
+ is_multiline)) {
+ return is_multiline;
+ }
+
+ return IsA<HTMLTextAreaElement>(*GetNode()) ||
+ HasContentEditableAttributeSet();
+}
+
+bool AXObject::IsRichlyEditable() const {
+ const Node* node = GetNode();
+ if (IsDetached() || !node)
+ return false;
+#if DCHECK_IS_ON() // Required in order to get Lifecycle().ToString()
+ DCHECK(GetDocument());
+ DCHECK_GE(GetDocument()->Lifecycle().GetState(),
+ DocumentLifecycle::kStyleClean)
+ << "Unclean document style at lifecycle state "
+ << GetDocument()->Lifecycle().ToString();
+#endif // DCHECK_IS_ON()
+
+ if (HasRichlyEditableStyle(*node))
+ return true;
+
+ return false;
}
AXObject* AXObject::LiveRegionRoot() const {
@@ -4106,13 +4101,11 @@ AXObject* AXObject::ParentObject() const {
// detached, but the children still exist. One example of this is when
// a <select size="1"> changes to <select size="2">, where the
// Role::kMenuListPopup is detached.
- if (!parent_) {
+ if (IsMissingParent()) {
DCHECK(!IsVirtualObject())
<< "A virtual object must have a parent, and cannot exist without one. "
"The parent is set when the object is constructed.";
- parent_ = ComputeParent();
- DCHECK(parent_ || IsA<Document>(GetNode()))
- << "The following node should have a parent: " << GetNode();
+ RepairMissingParent();
}
return parent_;
@@ -4151,23 +4144,30 @@ AXObject* AXObject::ContainerWidget() const {
return ancestor;
}
-// Only use layout object traversal for pseudo elements and their descendants.
+// Determine which traversal approach is used to get children of an object.
bool AXObject::ShouldUseLayoutObjectTraversalForChildren() const {
- if (!GetLayoutObject())
- return false;
+ // There are two types of traversal used to find AXObjects:
+ // 1. LayoutTreeBuilderTraversal, which takes FlatTreeTraversal and adds
+ // pseudo elements on top of that. This is the usual case. However, while this
+ // can add pseudo elements it cannot add important content descendants such as
+ // text and images. For this, LayoutObject traversal (#2) is required.
+ // 2. LayoutObject traversal, which just uses the children of a LayoutObject.
+
+ // Therefore, if the object is a pseudo element or pseudo element descendant,
+ // use LayoutObject traversal (#2) to find the children.
+ if (GetNode() && GetNode()->IsPseudoElement())
+ return true;
// If no node, this is an anonymous layout object. The only way this can be
// reached is inside a pseudo element subtree.
- if (!GetNode()) {
+ if (!GetNode() && GetLayoutObject()) {
DCHECK(GetLayoutObject()->IsAnonymous());
- DCHECK(AXObjectCacheImpl::IsPseudoElementDescendant(*GetLayoutObject()));
+ DCHECK(AXObjectCacheImpl::IsRelevantPseudoElementDescendant(
+ *GetLayoutObject()));
return true;
}
- // The only other case for using layout builder traversal is for a pseudo
- // element, such as ::before. Pseudo element child text and images are not
- // visibited by LayoutBuilderTraversal.
- return GetNode()->IsPseudoElement();
+ return false;
}
void AXObject::UpdateChildrenIfNecessary() {
@@ -4230,15 +4230,22 @@ void AXObject::ClearChildren() const {
// AccessibilityExposeIgnoredNodes().
// Loop through AXObject children.
-
-#if DCHECK_IS_ON()
- DCHECK(!is_adding_children_)
- << "Should not be attempting to clear children while in the middle of "
- "adding children on parent: "
+#if defined(AX_FAIL_FAST_BUILD)
+ CHECK(!is_adding_children_)
+ << "Should not attempt to simultaneously add and clear children on: "
<< ToString(true, true);
#endif
+ CHECK(!is_loading_inline_boxes_) << "Should not attempt to clear children "
+ "while loading inline text boxes: "
+ << ToString(true, true);
+
for (const auto& child : children_) {
+ // Check parent first, as the child might be several levels down if there
+ // are unincluded nodes in between, in which case the cached parent will
+ // also be a descendant (unlike children_, parent_ does not skip levels).
+ // Another case where the parent is not the same is when the child has been
+ // reparented using aria-owns.
if (child->CachedParentObject() == this)
child->DetachFromParent();
}
@@ -4248,6 +4255,12 @@ void AXObject::ClearChildren() const {
if (!GetNode())
return;
+ if (GetDocument()->IsFlatTreeTraversalForbidden()) {
+ // Cannot use layout tree builder traversal now, will have to rely on
+ // RepairParent() at a later point.
+ return;
+ }
+
// <slot> content is always included in the tree, so there is no need to
// iterate through the nodes. This also protects us against slot use "after
// poison", where attempts to access assigned nodes triggers a DCHECK.
@@ -4262,24 +4275,37 @@ void AXObject::ClearChildren() const {
// information with their original location in the DOM. Therefore, we need to
// ensure that in the accessibility tree no remnant information from the
// unflattened DOM tree remains, such as the cached parent.
- if (IsA<HTMLSlotElement>(GetNode()))
+
+ // TODO(crbug.com/1209216): Figure out why removing this causes a
+ // use-after-poison and possibly replace it with a better check.
+ HTMLSlotElement* slot = DynamicTo<HTMLSlotElement>(GetNode());
+ if (slot && slot->SupportsAssignment())
return;
+ // Detach children that were not cleared from first loop.
+ // These must have been an unincluded node who's parent is this,
+ // although it may now be included since the children were last updated.
for (Node* child_node = LayoutTreeBuilderTraversal::FirstChild(*GetNode());
child_node;
child_node = LayoutTreeBuilderTraversal::NextSibling(*child_node)) {
+ // Get the child object that should be detached from this parent.
AXObject* ax_child_from_node = AXObjectCache().Get(child_node);
if (ax_child_from_node &&
ax_child_from_node->CachedParentObject() == this) {
- // Child was not cleared from first loop.
- // It must have been an unincluded node who's parent is this,
- // although it may now be included since the children were last updated.
// Check current parent first. It may be owned by another node.
ax_child_from_node->DetachFromParent();
}
}
}
+Node* AXObject::GetNode() const {
+ return nullptr;
+}
+
+LayoutObject* AXObject::GetLayoutObject() const {
+ return nullptr;
+}
+
Element* AXObject::GetElement() const {
return DynamicTo<Element>(GetNode());
}
@@ -4976,11 +5002,11 @@ bool AXObject::RequestShowContextMenuAction() {
return OnNativeShowContextMenuAction();
}
-bool AXObject::InternalClearAccessibilityFocusAction() {
+bool AXObject::InternalSetAccessibilityFocusAction() {
return false;
}
-bool AXObject::InternalSetAccessibilityFocusAction() {
+bool AXObject::InternalClearAccessibilityFocusAction() {
return false;
}
@@ -5137,15 +5163,15 @@ bool AXObject::HasARIAOwns(Element* element) {
const AtomicString& aria_owns =
element->FastGetAttribute(html_names::kAriaOwnsAttr);
- // TODO: do we need to check !AriaOwnsElements.empty() ? Is that fundamentally
- // different from HasExplicitlySetAttrAssociatedElements()? And is an element
- // even necessary in the case of virtual nodes?
+ // TODO(accessibility): do we need to check !AriaOwnsElements.empty() ? Is
+ // that fundamentally different from HasExplicitlySetAttrAssociatedElements()?
+ // And is an element even necessary in the case of virtual nodes?
return !aria_owns.IsEmpty() ||
element->HasExplicitlySetAttrAssociatedElements(
html_names::kAriaOwnsAttr);
}
-ax::mojom::blink::Role AXObject::AriaRoleToWebCoreRole(const String& value) {
+ax::mojom::blink::Role AXObject::AriaRoleStringToRoleEnum(const String& value) {
DCHECK(!value.IsEmpty());
static const ARIARoleMap* role_map = CreateARIARoleMap();
@@ -5345,7 +5371,6 @@ bool AXObject::SupportsNameFromContents(bool recursive) const {
case ax::mojom::blink::Role::kNone:
case ax::mojom::blink::Role::kParagraph:
case ax::mojom::blink::Role::kPre:
- case ax::mojom::blink::Role::kPresentational:
case ax::mojom::blink::Role::kRegion:
// Spec says we should always expose the name on rows,
// but for performance reasons we only do it
@@ -5416,7 +5441,6 @@ bool AXObject::SupportsNameFromContents(bool recursive) const {
case ax::mojom::blink::Role::kColumn:
case ax::mojom::blink::Role::kDesktop:
case ax::mojom::blink::Role::kKeyboard:
- case ax::mojom::blink::Role::kIgnored:
case ax::mojom::blink::Role::kImeCandidate:
case ax::mojom::blink::Role::kListGrid:
case ax::mojom::blink::Role::kPane:
@@ -5465,18 +5489,62 @@ ax::mojom::blink::Role AXObject::ButtonRoleType() const {
}
// static
-const AtomicString& AXObject::RoleName(ax::mojom::blink::Role role) {
- static const Vector<AtomicString>* role_name_vector = CreateRoleNameVector();
+const AtomicString& AXObject::ARIARoleName(ax::mojom::blink::Role role) {
+ static const Vector<AtomicString>* aria_role_name_vector =
+ CreateARIARoleNameVector();
- return role_name_vector->at(static_cast<wtf_size_t>(role));
+ return aria_role_name_vector->at(static_cast<wtf_size_t>(role));
}
// static
-const AtomicString& AXObject::InternalRoleName(ax::mojom::blink::Role role) {
- static const Vector<AtomicString>* internal_role_name_vector =
- CreateInternalRoleNameVector();
+const AtomicString& AXObject::GetEquivalentAriaRoleName(
+ const ax::mojom::blink::Role role) {
+ // TODO(accessibilty) Why are some roles listed here and not others?
+ switch (role) {
+ case ax::mojom::blink::Role::kArticle:
+ case ax::mojom::blink::Role::kBanner:
+ case ax::mojom::blink::Role::kButton:
+ case ax::mojom::blink::Role::kComplementary:
+ case ax::mojom::blink::Role::kFigure:
+ case ax::mojom::blink::Role::kFooter:
+ case ax::mojom::blink::Role::kHeader:
+ case ax::mojom::blink::Role::kHeading:
+ case ax::mojom::blink::Role::kImage:
+ case ax::mojom::blink::Role::kMain:
+ case ax::mojom::blink::Role::kNavigation:
+ case ax::mojom::blink::Role::kRadioButton:
+ case ax::mojom::blink::Role::kRegion:
+ case ax::mojom::blink::Role::kSlider:
+ case ax::mojom::blink::Role::kTime:
+ return ARIARoleName(role);
+ default:
+ return g_null_atom;
+ }
+}
+
+const String AXObject::InternalRoleName(ax::mojom::blink::Role role) {
+ std::ostringstream role_name;
+ role_name << role;
+ // Convert from std::ostringstream to std::string, while removing "k" prefix.
+ // For example, kStaticText becomes StaticText.
+ // Many conversions, but this isn't used in performance-sensitive code.
+ std::string role_name_std = role_name.str().substr(1, std::string::npos);
+ String role_name_wtf_string = role_name_std.c_str();
+ return role_name_wtf_string;
+}
+
+// static
+const String AXObject::RoleName(ax::mojom::blink::Role role,
+ bool* is_internal) {
+ if (is_internal)
+ *is_internal = false;
+ if (const auto& role_name = ARIARoleName(role))
+ return role_name.GetString();
+
+ if (is_internal)
+ *is_internal = true;
- return internal_role_name_vector->at(static_cast<wtf_size_t>(role));
+ return InternalRoleName(role);
}
// static
@@ -5525,8 +5593,7 @@ String AXObject::ToString(bool verbose, bool cached_values_only) const {
// Build a friendly name for debugging the object.
// If verbose, build a longer name name in the form of:
// CheckBox axid#28 <input.someClass#cbox1> name="checkbox"
- String string_builder =
- AXObject::InternalRoleName(RoleValue()).GetString().EncodeForDebugging();
+ String string_builder = InternalRoleName(RoleValue()).EncodeForDebugging();
if (IsDetached())
string_builder = string_builder + " (detached)";
@@ -5534,8 +5601,8 @@ String AXObject::ToString(bool verbose, bool cached_values_only) const {
if (verbose) {
string_builder = string_builder + " axid#" + String::Number(AXObjectID());
// Add useful HTML element info, like <div.myClass#myId>.
- if (GetElement())
- string_builder = string_builder + " " + GetElementString(GetElement());
+ if (GetNode())
+ string_builder = string_builder + " " + GetNodeString(GetNode());
// Add properties of interest that often contribute to errors:
if (HasARIAOwns(GetElement())) {
@@ -5588,23 +5655,28 @@ String AXObject::ToString(bool verbose, bool cached_values_only) const {
string_builder = string_builder + " isDisplayLocked";
}
}
- if (const AXObject* aria_hidden_root = AriaHiddenRoot()) {
- string_builder = string_builder + " ariaHiddenRoot";
- if (aria_hidden_root != this) {
- string_builder =
- string_builder + GetElementString(aria_hidden_root->GetElement());
+ if (cached_values_only) {
+ if (cached_is_inert_or_aria_hidden_ && GetNode() && !GetNode()->IsInert())
+ string_builder = string_builder + " ariaHidden";
+ } else {
+ if (const AXObject* aria_hidden_root = AriaHiddenRoot()) {
+ string_builder = string_builder + " ariaHiddenRoot";
+ if (aria_hidden_root != this) {
+ string_builder =
+ string_builder + GetNodeString(aria_hidden_root->GetNode());
+ }
}
}
- if (GetDocument() && GetDocument()->Lifecycle().GetState() <
- DocumentLifecycle::kLayoutClean) {
- string_builder = string_builder + " styleInfoUnavailable";
- } else if (IsHiddenViaStyle()) {
+ if (cached_values_only ? cached_is_hidden_via_style : IsHiddenViaStyle())
string_builder = string_builder + " isHiddenViaCSS";
- }
if (GetNode() && GetNode()->IsInert())
string_builder = string_builder + " isInert";
- if (NeedsToUpdateChildren())
+ if (NeedsToUpdateChildren()) {
string_builder = string_builder + " needsToUpdateChildren";
+ } else if (!children_.IsEmpty()) {
+ string_builder = string_builder + " #children=";
+ string_builder = string_builder + String::Number(children_.size());
+ }
if (!GetLayoutObject())
string_builder = string_builder + " missingLayout";