1 /*
2 * Copyright (C) 2008, 2009, 2011 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29 #include "third_party/blink/renderer/modules/accessibility/ax_object.h"
30
31 #include <algorithm>
32 #include "base/strings/string_util.h"
33 #include "third_party/blink/public/common/input/web_menu_source_type.h"
34 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
35 #include "third_party/blink/renderer/core/aom/accessible_node.h"
36 #include "third_party/blink/renderer/core/aom/accessible_node_list.h"
37 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
38 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
39 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
40 #include "third_party/blink/renderer/core/dom/focus_params.h"
41 #include "third_party/blink/renderer/core/dom/node_computed_style.h"
42 #include "third_party/blink/renderer/core/frame/local_frame.h"
43 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
44 #include "third_party/blink/renderer/core/frame/settings.h"
45 #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
46 #include "third_party/blink/renderer/core/html/custom/element_internals.h"
47 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
48 #include "third_party/blink/renderer/core/html/forms/html_select_element.h"
49 #include "third_party/blink/renderer/core/html/html_dialog_element.h"
50 #include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
51 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
52 #include "third_party/blink/renderer/core/input/context_menu_allowed_scope.h"
53 #include "third_party/blink/renderer/core/input/event_handler.h"
54 #include "third_party/blink/renderer/core/input_type_names.h"
55 #include "third_party/blink/renderer/core/layout/layout_box.h"
56 #include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
57 #include "third_party/blink/renderer/core/layout/layout_view.h"
58 #include "third_party/blink/renderer/core/page/chrome_client.h"
59 #include "third_party/blink/renderer/core/page/focus_controller.h"
60 #include "third_party/blink/renderer/core/page/page.h"
61 #include "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h"
62 #include "third_party/blink/renderer/core/svg/svg_element.h"
63 #include "third_party/blink/renderer/core/svg/svg_g_element.h"
64 #include "third_party/blink/renderer/core/svg/svg_style_element.h"
65 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list.h"
66 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list_option.h"
67 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list_popup.h"
68 #include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
69 #include "third_party/blink/renderer/modules/accessibility/ax_range.h"
70 #include "third_party/blink/renderer/modules/accessibility/ax_selection.h"
71 #include "third_party/blink/renderer/modules/accessibility/ax_sparse_attribute_setter.h"
72 #include "third_party/blink/renderer/platform/instrumentation/tracing/trace_event.h"
73 #include "third_party/blink/renderer/platform/language.h"
74 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
75 #include "third_party/blink/renderer/platform/text/platform_locale.h"
76 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
77 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
78 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
79 #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
80 #include "third_party/skia/include/core/SkMatrix44.h"
81 #include "ui/accessibility/ax_node_data.h"
82 #include "ui/accessibility/ax_role_properties.h"
83
84 namespace blink {
85
86 namespace {
87
88 struct RoleHashTraits : HashTraits<ax::mojom::blink::Role> {
89 static const bool kEmptyValueIsZero = true;
EmptyValueblink::__anond856eca50111::RoleHashTraits90 static ax::mojom::blink::Role EmptyValue() {
91 return ax::mojom::blink::Role::kUnknown;
92 }
93 };
94
95 using ARIARoleMap = HashMap<String,
96 ax::mojom::blink::Role,
97 CaseFoldingHash,
98 HashTraits<String>,
99 RoleHashTraits>;
100
101 struct RoleEntry {
102 const char* aria_role;
103 ax::mojom::blink::Role webcore_role;
104 };
105
106 // Mapping of ARIA role name to internal role name.
107 const RoleEntry kRoles[] = {
108 {"alert", ax::mojom::blink::Role::kAlert},
109 {"alertdialog", ax::mojom::blink::Role::kAlertDialog},
110 {"application", ax::mojom::blink::Role::kApplication},
111 {"article", ax::mojom::blink::Role::kArticle},
112 {"banner", ax::mojom::blink::Role::kBanner},
113 {"blockquote", ax::mojom::blink::Role::kBlockquote},
114 {"button", ax::mojom::blink::Role::kButton},
115 {"caption", ax::mojom::blink::Role::kCaption},
116 {"cell", ax::mojom::blink::Role::kCell},
117 {"code", ax::mojom::blink::Role::kCode},
118 {"checkbox", ax::mojom::blink::Role::kCheckBox},
119 {"columnheader", ax::mojom::blink::Role::kColumnHeader},
120 {"combobox", ax::mojom::blink::Role::kComboBoxGrouping},
121 {"comment", ax::mojom::blink::Role::kComment},
122 {"complementary", ax::mojom::blink::Role::kComplementary},
123 {"contentinfo", ax::mojom::blink::Role::kContentInfo},
124 {"definition", ax::mojom::blink::Role::kDefinition},
125 {"deletion", ax::mojom::blink::Role::kContentDeletion},
126 {"dialog", ax::mojom::blink::Role::kDialog},
127 {"directory", ax::mojom::blink::Role::kDirectory},
128 // -------------------------------------------------
129 // DPub Roles:
130 // www.w3.org/TR/dpub-aam-1.0/#mapping_role_table
131 {"doc-abstract", ax::mojom::blink::Role::kDocAbstract},
132 {"doc-acknowledgments", ax::mojom::blink::Role::kDocAcknowledgments},
133 {"doc-afterword", ax::mojom::blink::Role::kDocAfterword},
134 {"doc-appendix", ax::mojom::blink::Role::kDocAppendix},
135 {"doc-backlink", ax::mojom::blink::Role::kDocBackLink},
136 {"doc-biblioentry", ax::mojom::blink::Role::kDocBiblioEntry},
137 {"doc-bibliography", ax::mojom::blink::Role::kDocBibliography},
138 {"doc-biblioref", ax::mojom::blink::Role::kDocBiblioRef},
139 {"doc-chapter", ax::mojom::blink::Role::kDocChapter},
140 {"doc-colophon", ax::mojom::blink::Role::kDocColophon},
141 {"doc-conclusion", ax::mojom::blink::Role::kDocConclusion},
142 {"doc-cover", ax::mojom::blink::Role::kDocCover},
143 {"doc-credit", ax::mojom::blink::Role::kDocCredit},
144 {"doc-credits", ax::mojom::blink::Role::kDocCredits},
145 {"doc-dedication", ax::mojom::blink::Role::kDocDedication},
146 {"doc-endnote", ax::mojom::blink::Role::kDocEndnote},
147 {"doc-endnotes", ax::mojom::blink::Role::kDocEndnotes},
148 {"doc-epigraph", ax::mojom::blink::Role::kDocEpigraph},
149 {"doc-epilogue", ax::mojom::blink::Role::kDocEpilogue},
150 {"doc-errata", ax::mojom::blink::Role::kDocErrata},
151 {"doc-example", ax::mojom::blink::Role::kDocExample},
152 {"doc-footnote", ax::mojom::blink::Role::kDocFootnote},
153 {"doc-foreword", ax::mojom::blink::Role::kDocForeword},
154 {"doc-glossary", ax::mojom::blink::Role::kDocGlossary},
155 {"doc-glossref", ax::mojom::blink::Role::kDocGlossRef},
156 {"doc-index", ax::mojom::blink::Role::kDocIndex},
157 {"doc-introduction", ax::mojom::blink::Role::kDocIntroduction},
158 {"doc-noteref", ax::mojom::blink::Role::kDocNoteRef},
159 {"doc-notice", ax::mojom::blink::Role::kDocNotice},
160 {"doc-pagebreak", ax::mojom::blink::Role::kDocPageBreak},
161 {"doc-pagefooter", ax::mojom::blink::Role::kDocPageFooter},
162 {"doc-pageheader", ax::mojom::blink::Role::kDocPageHeader},
163 {"doc-pagelist", ax::mojom::blink::Role::kDocPageList},
164 {"doc-part", ax::mojom::blink::Role::kDocPart},
165 {"doc-preface", ax::mojom::blink::Role::kDocPreface},
166 {"doc-prologue", ax::mojom::blink::Role::kDocPrologue},
167 {"doc-pullquote", ax::mojom::blink::Role::kDocPullquote},
168 {"doc-qna", ax::mojom::blink::Role::kDocQna},
169 {"doc-subtitle", ax::mojom::blink::Role::kDocSubtitle},
170 {"doc-tip", ax::mojom::blink::Role::kDocTip},
171 {"doc-toc", ax::mojom::blink::Role::kDocToc},
172 // End DPub roles.
173 // -------------------------------------------------
174 {"document", ax::mojom::blink::Role::kDocument},
175 {"emphasis", ax::mojom::blink::Role::kEmphasis},
176 {"feed", ax::mojom::blink::Role::kFeed},
177 {"figure", ax::mojom::blink::Role::kFigure},
178 {"form", ax::mojom::blink::Role::kForm},
179 {"generic", ax::mojom::blink::Role::kGenericContainer},
180 // -------------------------------------------------
181 // ARIA Graphics module roles:
182 // https://rawgit.com/w3c/graphics-aam/master/
183 {"graphics-document", ax::mojom::blink::Role::kGraphicsDocument},
184 {"graphics-object", ax::mojom::blink::Role::kGraphicsObject},
185 {"graphics-symbol", ax::mojom::blink::Role::kGraphicsSymbol},
186 // End ARIA Graphics module roles.
187 // -------------------------------------------------
188 {"grid", ax::mojom::blink::Role::kGrid},
189 {"gridcell", ax::mojom::blink::Role::kCell},
190 {"group", ax::mojom::blink::Role::kGroup},
191 {"heading", ax::mojom::blink::Role::kHeading},
192 {"img", ax::mojom::blink::Role::kImage},
193 {"insertion", ax::mojom::blink::Role::kContentInsertion},
194 {"link", ax::mojom::blink::Role::kLink},
195 {"list", ax::mojom::blink::Role::kList},
196 {"listbox", ax::mojom::blink::Role::kListBox},
197 {"listitem", ax::mojom::blink::Role::kListItem},
198 {"log", ax::mojom::blink::Role::kLog},
199 {"main", ax::mojom::blink::Role::kMain},
200 {"marquee", ax::mojom::blink::Role::kMarquee},
201 {"math", ax::mojom::blink::Role::kMath},
202 {"menu", ax::mojom::blink::Role::kMenu},
203 {"menubar", ax::mojom::blink::Role::kMenuBar},
204 {"menuitem", ax::mojom::blink::Role::kMenuItem},
205 {"menuitemcheckbox", ax::mojom::blink::Role::kMenuItemCheckBox},
206 {"menuitemradio", ax::mojom::blink::Role::kMenuItemRadio},
207 {"mark", ax::mojom::blink::Role::kMark},
208 {"meter", ax::mojom::blink::Role::kMeter},
209 {"navigation", ax::mojom::blink::Role::kNavigation},
210 {"none", ax::mojom::blink::Role::kNone},
211 {"note", ax::mojom::blink::Role::kNote},
212 {"option", ax::mojom::blink::Role::kListBoxOption},
213 {"paragraph", ax::mojom::blink::Role::kParagraph},
214 {"presentation", ax::mojom::blink::Role::kPresentational},
215 {"progressbar", ax::mojom::blink::Role::kProgressIndicator},
216 {"radio", ax::mojom::blink::Role::kRadioButton},
217 {"radiogroup", ax::mojom::blink::Role::kRadioGroup},
218 // TODO(accessibility) region should only be mapped
219 // if name present. See http://crbug.com/840819.
220 {"region", ax::mojom::blink::Role::kRegion},
221 {"row", ax::mojom::blink::Role::kRow},
222 {"rowgroup", ax::mojom::blink::Role::kRowGroup},
223 {"rowheader", ax::mojom::blink::Role::kRowHeader},
224 {"scrollbar", ax::mojom::blink::Role::kScrollBar},
225 {"search", ax::mojom::blink::Role::kSearch},
226 {"searchbox", ax::mojom::blink::Role::kSearchBox},
227 {"separator", ax::mojom::blink::Role::kSplitter},
228 {"slider", ax::mojom::blink::Role::kSlider},
229 {"spinbutton", ax::mojom::blink::Role::kSpinButton},
230 {"status", ax::mojom::blink::Role::kStatus},
231 {"strong", ax::mojom::blink::Role::kStrong},
232 {"suggestion", ax::mojom::blink::Role::kSuggestion},
233 {"switch", ax::mojom::blink::Role::kSwitch},
234 {"tab", ax::mojom::blink::Role::kTab},
235 {"table", ax::mojom::blink::Role::kTable},
236 {"tablist", ax::mojom::blink::Role::kTabList},
237 {"tabpanel", ax::mojom::blink::Role::kTabPanel},
238 {"term", ax::mojom::blink::Role::kTerm},
239 {"text", ax::mojom::blink::Role::kStaticText},
240 {"textbox", ax::mojom::blink::Role::kTextField},
241 {"time", ax::mojom::blink::Role::kTime},
242 {"timer", ax::mojom::blink::Role::kTimer},
243 {"toolbar", ax::mojom::blink::Role::kToolbar},
244 {"tooltip", ax::mojom::blink::Role::kTooltip},
245 {"tree", ax::mojom::blink::Role::kTree},
246 {"treegrid", ax::mojom::blink::Role::kTreeGrid},
247 {"treeitem", ax::mojom::blink::Role::kTreeItem}};
248
249 struct InternalRoleEntry {
250 ax::mojom::blink::Role webcore_role;
251 const char* internal_role_name;
252 };
253
254 const InternalRoleEntry kInternalRoles[] = {
255 {ax::mojom::blink::Role::kNone, "None"},
256 {ax::mojom::blink::Role::kAbbr, "Abbr"},
257 {ax::mojom::blink::Role::kAlertDialog, "AlertDialog"},
258 {ax::mojom::blink::Role::kAlert, "Alert"},
259 {ax::mojom::blink::Role::kAnchor, "Anchor"},
260 {ax::mojom::blink::Role::kComment, "Comment"},
261 {ax::mojom::blink::Role::kApplication, "Application"},
262 {ax::mojom::blink::Role::kArticle, "Article"},
263 {ax::mojom::blink::Role::kAudio, "Audio"},
264 {ax::mojom::blink::Role::kBanner, "Banner"},
265 {ax::mojom::blink::Role::kBlockquote, "Blockquote"},
266 {ax::mojom::blink::Role::kButton, "Button"},
267 {ax::mojom::blink::Role::kCanvas, "Canvas"},
268 {ax::mojom::blink::Role::kCaption, "Caption"},
269 {ax::mojom::blink::Role::kCaret, "Caret"},
270 {ax::mojom::blink::Role::kCell, "Cell"},
271 {ax::mojom::blink::Role::kCheckBox, "CheckBox"},
272 {ax::mojom::blink::Role::kClient, "Client"},
273 {ax::mojom::blink::Role::kCode, "Code"},
274 {ax::mojom::blink::Role::kColorWell, "ColorWell"},
275 {ax::mojom::blink::Role::kColumnHeader, "ColumnHeader"},
276 {ax::mojom::blink::Role::kColumn, "Column"},
277 {ax::mojom::blink::Role::kComboBoxGrouping, "ComboBox"},
278 {ax::mojom::blink::Role::kComboBoxMenuButton, "ComboBox"},
279 {ax::mojom::blink::Role::kComplementary, "Complementary"},
280 {ax::mojom::blink::Role::kContentDeletion, "ContentDeletion"},
281 {ax::mojom::blink::Role::kContentInsertion, "ContentInsertion"},
282 {ax::mojom::blink::Role::kContentInfo, "ContentInfo"},
283 {ax::mojom::blink::Role::kDate, "Date"},
284 {ax::mojom::blink::Role::kDateTime, "DateTime"},
285 {ax::mojom::blink::Role::kDefinition, "Definition"},
286 {ax::mojom::blink::Role::kDescriptionListDetail, "DescriptionListDetail"},
287 {ax::mojom::blink::Role::kDescriptionList, "DescriptionList"},
288 {ax::mojom::blink::Role::kDescriptionListTerm, "DescriptionListTerm"},
289 {ax::mojom::blink::Role::kDesktop, "Desktop"},
290 {ax::mojom::blink::Role::kDetails, "Details"},
291 {ax::mojom::blink::Role::kDialog, "Dialog"},
292 {ax::mojom::blink::Role::kDirectory, "Directory"},
293 {ax::mojom::blink::Role::kDisclosureTriangle, "DisclosureTriangle"},
294 // --------------------------------------------------------------
295 // DPub Roles:
296 // https://www.w3.org/TR/dpub-aam-1.0/#mapping_role_table
297 {ax::mojom::blink::Role::kDocAbstract, "DocAbstract"},
298 {ax::mojom::blink::Role::kDocAcknowledgments, "DocAcknowledgments"},
299 {ax::mojom::blink::Role::kDocAfterword, "DocAfterword"},
300 {ax::mojom::blink::Role::kDocAppendix, "DocAppendix"},
301 {ax::mojom::blink::Role::kDocBackLink, "DocBackLink"},
302 {ax::mojom::blink::Role::kDocBiblioEntry, "DocBiblioentry"},
303 {ax::mojom::blink::Role::kDocBibliography, "DocBibliography"},
304 {ax::mojom::blink::Role::kDocBiblioRef, "DocBiblioref"},
305 {ax::mojom::blink::Role::kDocChapter, "DocChapter"},
306 {ax::mojom::blink::Role::kDocColophon, "DocColophon"},
307 {ax::mojom::blink::Role::kDocConclusion, "DocConclusion"},
308 {ax::mojom::blink::Role::kDocCover, "DocCover"},
309 {ax::mojom::blink::Role::kDocCredit, "DocCredit"},
310 {ax::mojom::blink::Role::kDocCredits, "DocCredits"},
311 {ax::mojom::blink::Role::kDocDedication, "DocDedication"},
312 {ax::mojom::blink::Role::kDocEndnote, "DocEndnote"},
313 {ax::mojom::blink::Role::kDocEndnotes, "DocEndnotes"},
314 {ax::mojom::blink::Role::kDocEpigraph, "DocEpigraph"},
315 {ax::mojom::blink::Role::kDocEpilogue, "DocEpilogue"},
316 {ax::mojom::blink::Role::kDocErrata, "DocErrata"},
317 {ax::mojom::blink::Role::kDocExample, "DocExample"},
318 {ax::mojom::blink::Role::kDocFootnote, "DocFootnote"},
319 {ax::mojom::blink::Role::kDocForeword, "DocForeword"},
320 {ax::mojom::blink::Role::kDocGlossary, "DocGlossary"},
321 {ax::mojom::blink::Role::kDocGlossRef, "DocGlossref"},
322 {ax::mojom::blink::Role::kDocIndex, "DocIndex"},
323 {ax::mojom::blink::Role::kDocIntroduction, "DocIntroduction"},
324 {ax::mojom::blink::Role::kDocNoteRef, "DocNoteref"},
325 {ax::mojom::blink::Role::kDocNotice, "DocNotice"},
326 {ax::mojom::blink::Role::kDocPageBreak, "DocPagebreak"},
327 {ax::mojom::blink::Role::kDocPageFooter, "DocPageFooter"},
328 {ax::mojom::blink::Role::kDocPageHeader, "DocPageHeader"},
329 {ax::mojom::blink::Role::kDocPageList, "DocPagelist"},
330 {ax::mojom::blink::Role::kDocPart, "DocPart"},
331 {ax::mojom::blink::Role::kDocPreface, "DocPreface"},
332 {ax::mojom::blink::Role::kDocPrologue, "DocPrologue"},
333 {ax::mojom::blink::Role::kDocPullquote, "DocPullquote"},
334 {ax::mojom::blink::Role::kDocQna, "DocQna"},
335 {ax::mojom::blink::Role::kDocSubtitle, "DocSubtitle"},
336 {ax::mojom::blink::Role::kDocTip, "DocTip"},
337 {ax::mojom::blink::Role::kDocToc, "DocToc"},
338 // End DPub roles.
339 // --------------------------------------------------------------
340 {ax::mojom::blink::Role::kDocument, "Document"},
341 {ax::mojom::blink::Role::kEmbeddedObject, "EmbeddedObject"},
342 {ax::mojom::blink::Role::kEmphasis, "Emphasis"},
343 {ax::mojom::blink::Role::kFeed, "feed"},
344 {ax::mojom::blink::Role::kFigcaption, "Figcaption"},
345 {ax::mojom::blink::Role::kFigure, "Figure"},
346 {ax::mojom::blink::Role::kFooter, "Footer"},
347 {ax::mojom::blink::Role::kFooterAsNonLandmark, "FooterAsNonLandmark"},
348 {ax::mojom::blink::Role::kForm, "Form"},
349 {ax::mojom::blink::Role::kGenericContainer, "GenericContainer"},
350 // --------------------------------------------------------------
351 // ARIA Graphics module roles:
352 // https://rawgit.com/w3c/graphics-aam/master/#mapping_role_table
353 {ax::mojom::blink::Role::kGraphicsDocument, "GraphicsDocument"},
354 {ax::mojom::blink::Role::kGraphicsObject, "GraphicsObject"},
355 {ax::mojom::blink::Role::kGraphicsSymbol, "GraphicsSymbol"},
356 // End ARIA Graphics module roles.
357 // --------------------------------------------------------------
358 {ax::mojom::blink::Role::kGrid, "Grid"},
359 {ax::mojom::blink::Role::kGroup, "Group"},
360 {ax::mojom::blink::Role::kHeader, "Header"},
361 {ax::mojom::blink::Role::kHeaderAsNonLandmark, "HeaderAsNonLandmark"},
362 {ax::mojom::blink::Role::kHeading, "Heading"},
363 {ax::mojom::blink::Role::kIframePresentational, "IframePresentational"},
364 {ax::mojom::blink::Role::kIframe, "Iframe"},
365 {ax::mojom::blink::Role::kIgnored, "Ignored"},
366 {ax::mojom::blink::Role::kImageMap, "ImageMap"},
367 {ax::mojom::blink::Role::kImage, "Image"},
368 {ax::mojom::blink::Role::kImeCandidate, "ImeCandidate"},
369 {ax::mojom::blink::Role::kInlineTextBox, "InlineTextBox"},
370 {ax::mojom::blink::Role::kInputTime, "InputTime"},
371 {ax::mojom::blink::Role::kKeyboard, "Keyboard"},
372 {ax::mojom::blink::Role::kLabelText, "Label"},
373 {ax::mojom::blink::Role::kLayoutTable, "LayoutTable"},
374 {ax::mojom::blink::Role::kLayoutTableCell, "LayoutCellTable"},
375 {ax::mojom::blink::Role::kLayoutTableRow, "LayoutRowTable"},
376 {ax::mojom::blink::Role::kLegend, "Legend"},
377 {ax::mojom::blink::Role::kLink, "Link"},
378 {ax::mojom::blink::Role::kLineBreak, "LineBreak"},
379 {ax::mojom::blink::Role::kListBox, "ListBox"},
380 {ax::mojom::blink::Role::kListBoxOption, "ListBoxOption"},
381 {ax::mojom::blink::Role::kListGrid, "ListGrid"},
382 {ax::mojom::blink::Role::kListItem, "ListItem"},
383 {ax::mojom::blink::Role::kListMarker, "ListMarker"},
384 {ax::mojom::blink::Role::kList, "List"},
385 {ax::mojom::blink::Role::kLog, "Log"},
386 {ax::mojom::blink::Role::kMain, "Main"},
387 {ax::mojom::blink::Role::kMark, "Mark"},
388 {ax::mojom::blink::Role::kMarquee, "Marquee"},
389 {ax::mojom::blink::Role::kMath, "Math"},
390 {ax::mojom::blink::Role::kMenuBar, "MenuBar"},
391 {ax::mojom::blink::Role::kMenuItem, "MenuItem"},
392 {ax::mojom::blink::Role::kMenuItemCheckBox, "MenuItemCheckBox"},
393 {ax::mojom::blink::Role::kMenuItemRadio, "MenuItemRadio"},
394 {ax::mojom::blink::Role::kMenuListOption, "MenuListOption"},
395 {ax::mojom::blink::Role::kMenuListPopup, "MenuListPopup"},
396 {ax::mojom::blink::Role::kMenu, "Menu"},
397 {ax::mojom::blink::Role::kMeter, "Meter"},
398 {ax::mojom::blink::Role::kNavigation, "Navigation"},
399 {ax::mojom::blink::Role::kNote, "Note"},
400 {ax::mojom::blink::Role::kPane, "Pane"},
401 {ax::mojom::blink::Role::kParagraph, "Paragraph"},
402 {ax::mojom::blink::Role::kPdfActionableHighlight, "PdfActionableHighlight"},
403 {ax::mojom::blink::Role::kPluginObject, "PluginObject"},
404 {ax::mojom::blink::Role::kPopUpButton, "PopUpButton"},
405 {ax::mojom::blink::Role::kPortal, "Portal"},
406 {ax::mojom::blink::Role::kPre, "Pre"},
407 {ax::mojom::blink::Role::kPresentational, "Presentational"},
408 {ax::mojom::blink::Role::kProgressIndicator, "ProgressIndicator"},
409 {ax::mojom::blink::Role::kRadioButton, "RadioButton"},
410 {ax::mojom::blink::Role::kRadioGroup, "RadioGroup"},
411 {ax::mojom::blink::Role::kRegion, "Region"},
412 {ax::mojom::blink::Role::kRootWebArea, "WebArea"},
413 {ax::mojom::blink::Role::kRow, "Row"},
414 {ax::mojom::blink::Role::kRowGroup, "RowGroup"},
415 {ax::mojom::blink::Role::kRowHeader, "RowHeader"},
416 {ax::mojom::blink::Role::kRuby, "Ruby"},
417 {ax::mojom::blink::Role::kRubyAnnotation, "RubyAnnotation"},
418 {ax::mojom::blink::Role::kSection, "Section"},
419 {ax::mojom::blink::Role::kSvgRoot, "SVGRoot"},
420 {ax::mojom::blink::Role::kScrollBar, "ScrollBar"},
421 {ax::mojom::blink::Role::kScrollView, "ScrollView"},
422 {ax::mojom::blink::Role::kSearch, "Search"},
423 {ax::mojom::blink::Role::kSearchBox, "SearchBox"},
424 {ax::mojom::blink::Role::kSlider, "Slider"},
425 {ax::mojom::blink::Role::kSliderThumb, "SliderThumb"},
426 {ax::mojom::blink::Role::kSpinButton, "SpinButton"},
427 {ax::mojom::blink::Role::kSplitter, "Splitter"},
428 {ax::mojom::blink::Role::kStaticText, "StaticText"},
429 {ax::mojom::blink::Role::kStatus, "Status"},
430 {ax::mojom::blink::Role::kStrong, "Strong"},
431 {ax::mojom::blink::Role::kSuggestion, "Suggestion"},
432 {ax::mojom::blink::Role::kSwitch, "Switch"},
433 {ax::mojom::blink::Role::kTab, "Tab"},
434 {ax::mojom::blink::Role::kTabList, "TabList"},
435 {ax::mojom::blink::Role::kTabPanel, "TabPanel"},
436 {ax::mojom::blink::Role::kTable, "Table"},
437 {ax::mojom::blink::Role::kTableHeaderContainer, "TableHeaderContainer"},
438 {ax::mojom::blink::Role::kTerm, "Term"},
439 {ax::mojom::blink::Role::kTextField, "TextField"},
440 {ax::mojom::blink::Role::kTextFieldWithComboBox, "ComboBox"},
441 {ax::mojom::blink::Role::kTime, "Time"},
442 {ax::mojom::blink::Role::kTimer, "Timer"},
443 {ax::mojom::blink::Role::kTitleBar, "TitleBar"},
444 {ax::mojom::blink::Role::kToggleButton, "ToggleButton"},
445 {ax::mojom::blink::Role::kToolbar, "Toolbar"},
446 {ax::mojom::blink::Role::kTreeGrid, "TreeGrid"},
447 {ax::mojom::blink::Role::kTreeItem, "TreeItem"},
448 {ax::mojom::blink::Role::kTree, "Tree"},
449 {ax::mojom::blink::Role::kTooltip, "UserInterfaceTooltip"},
450 {ax::mojom::blink::Role::kUnknown, "Unknown"},
451 {ax::mojom::blink::Role::kVideo, "Video"},
452 {ax::mojom::blink::Role::kWebArea, "WebArea"},
453 {ax::mojom::blink::Role::kWebView, "WebView"},
454 {ax::mojom::blink::Role::kWindow, "Window"}};
455
456 static_assert(base::size(kInternalRoles) ==
457 static_cast<size_t>(ax::mojom::blink::Role::kMaxValue) + 1,
458 "Not all internal roles have an entry in internalRoles array");
459
460 // Roles which we need to map in the other direction
461 const RoleEntry kReverseRoles[] = {
462 {"banner", ax::mojom::blink::Role::kHeader},
463 {"button", ax::mojom::blink::Role::kToggleButton},
464 {"combobox", ax::mojom::blink::Role::kPopUpButton},
465 {"contentinfo", ax::mojom::blink::Role::kFooter},
466 {"menuitem", ax::mojom::blink::Role::kMenuListOption},
467 {"progressbar", ax::mojom::blink::Role::kMeter},
468 {"region", ax::mojom::blink::Role::kSection},
469 {"textbox", ax::mojom::blink::Role::kTextField},
470 {"combobox", ax::mojom::blink::Role::kComboBoxMenuButton},
471 {"combobox", ax::mojom::blink::Role::kTextFieldWithComboBox}};
472
CreateARIARoleMap()473 static ARIARoleMap* CreateARIARoleMap() {
474 ARIARoleMap* role_map = new ARIARoleMap;
475
476 for (size_t i = 0; i < base::size(kRoles); ++i)
477 role_map->Set(String(kRoles[i].aria_role), kRoles[i].webcore_role);
478
479 return role_map;
480 }
481
CreateRoleNameVector()482 static Vector<AtomicString>* CreateRoleNameVector() {
483 Vector<AtomicString>* role_name_vector =
484 new Vector<AtomicString>(base::size(kInternalRoles));
485 for (wtf_size_t i = 0; i < base::size(kInternalRoles); i++)
486 (*role_name_vector)[i] = g_null_atom;
487
488 for (wtf_size_t i = 0; i < base::size(kRoles); ++i) {
489 (*role_name_vector)[static_cast<wtf_size_t>(kRoles[i].webcore_role)] =
490 AtomicString(kRoles[i].aria_role);
491 }
492
493 for (wtf_size_t i = 0; i < base::size(kReverseRoles); ++i) {
494 (*role_name_vector)[static_cast<wtf_size_t>(
495 kReverseRoles[i].webcore_role)] =
496 AtomicString(kReverseRoles[i].aria_role);
497 }
498
499 return role_name_vector;
500 }
501
CreateInternalRoleNameVector()502 static Vector<AtomicString>* CreateInternalRoleNameVector() {
503 Vector<AtomicString>* internal_role_name_vector =
504 new Vector<AtomicString>(base::size(kInternalRoles));
505 for (wtf_size_t i = 0; i < base::size(kInternalRoles); i++) {
506 (*internal_role_name_vector)[static_cast<wtf_size_t>(
507 kInternalRoles[i].webcore_role)] =
508 AtomicString(kInternalRoles[i].internal_role_name);
509 }
510
511 return internal_role_name_vector;
512 }
513
GetActiveDialogElement(Node * node)514 HTMLDialogElement* GetActiveDialogElement(Node* node) {
515 return node->GetDocument().ActiveModalDialog();
516 }
517
518 // TODO(dmazzoni): replace this with a call to RoleName().
GetEquivalentAriaRoleString(const ax::mojom::blink::Role role)519 std::string GetEquivalentAriaRoleString(const ax::mojom::blink::Role role) {
520 switch (role) {
521 case ax::mojom::blink::Role::kArticle:
522 return "article";
523 case ax::mojom::blink::Role::kBanner:
524 return "banner";
525 case ax::mojom::blink::Role::kButton:
526 return "button";
527 case ax::mojom::blink::Role::kComplementary:
528 return "complementary";
529 case ax::mojom::blink::Role::kFigure:
530 return "figure";
531 case ax::mojom::blink::Role::kFooter:
532 return "contentinfo";
533 case ax::mojom::blink::Role::kHeader:
534 return "banner";
535 case ax::mojom::blink::Role::kHeading:
536 return "heading";
537 case ax::mojom::blink::Role::kImage:
538 return "img";
539 case ax::mojom::blink::Role::kMain:
540 return "main";
541 case ax::mojom::blink::Role::kNavigation:
542 return "navigation";
543 case ax::mojom::blink::Role::kRadioButton:
544 return "radio";
545 case ax::mojom::blink::Role::kRegion:
546 return "region";
547 case ax::mojom::blink::Role::kSection:
548 // A <section> element uses the 'region' ARIA role mapping.
549 return "region";
550 case ax::mojom::blink::Role::kSlider:
551 return "slider";
552 case ax::mojom::blink::Role::kTime:
553 return "time";
554 default:
555 break;
556 }
557
558 return std::string();
559 }
560
561 } // namespace
562
563 unsigned AXObject::number_of_live_ax_objects_ = 0;
564
AXObject(AXObjectCacheImpl & ax_object_cache)565 AXObject::AXObject(AXObjectCacheImpl& ax_object_cache)
566 : id_(0),
567 have_children_(false),
568 role_(ax::mojom::blink::Role::kUnknown),
569 aria_role_(ax::mojom::blink::Role::kUnknown),
570 last_known_is_ignored_value_(kDefaultBehavior),
571 last_known_is_ignored_but_included_in_tree_value_(kDefaultBehavior),
572 explicit_container_id_(0),
573 parent_(nullptr),
574 last_modification_count_(-1),
575 cached_is_ignored_(false),
576 cached_is_ignored_but_included_in_tree_(false),
577 cached_is_inert_or_aria_hidden_(false),
578 cached_is_descendant_of_leaf_node_(false),
579 cached_is_descendant_of_disabled_node_(false),
580 cached_has_inherited_presentational_role_(false),
581 cached_is_editable_root_(false),
582 cached_live_region_root_(nullptr),
583 cached_aria_column_index_(0),
584 cached_aria_row_index_(0),
585 ax_object_cache_(&ax_object_cache) {
586 ++number_of_live_ax_objects_;
587 }
588
~AXObject()589 AXObject::~AXObject() {
590 DCHECK(IsDetached());
591 --number_of_live_ax_objects_;
592 }
593
Init()594 void AXObject::Init() {
595 role_ = DetermineAccessibilityRole();
596 }
597
Detach()598 void AXObject::Detach() {
599 // Clear any children and call detachFromParent on them so that
600 // no children are left with dangling pointers to their parent.
601 ClearChildren();
602
603 ax_object_cache_ = nullptr;
604 }
605
IsDetached() const606 bool AXObject::IsDetached() const {
607 return !ax_object_cache_;
608 }
609
SetParent(AXObject * parent)610 void AXObject::SetParent(AXObject* parent) {
611 parent_ = parent;
612 }
613
GetAOMPropertyOrARIAAttribute(AOMStringProperty property) const614 const AtomicString& AXObject::GetAOMPropertyOrARIAAttribute(
615 AOMStringProperty property) const {
616 Element* element = this->GetElement();
617 if (!element)
618 return g_null_atom;
619
620 return AccessibleNode::GetPropertyOrARIAAttribute(element, property);
621 }
622
GetAOMPropertyOrARIAAttribute(AOMRelationProperty property) const623 Element* AXObject::GetAOMPropertyOrARIAAttribute(
624 AOMRelationProperty property) const {
625 Element* element = this->GetElement();
626 if (!element)
627 return nullptr;
628
629 return AccessibleNode::GetPropertyOrARIAAttribute(element, property);
630 }
631
HasAOMProperty(AOMRelationListProperty property,HeapVector<Member<Element>> & result) const632 bool AXObject::HasAOMProperty(AOMRelationListProperty property,
633 HeapVector<Member<Element>>& result) const {
634 Element* element = this->GetElement();
635 if (!element)
636 return false;
637
638 return AccessibleNode::GetProperty(element, property, result);
639 }
640
HasAOMPropertyOrARIAAttribute(AOMRelationListProperty property,HeapVector<Member<Element>> & result) const641 bool AXObject::HasAOMPropertyOrARIAAttribute(
642 AOMRelationListProperty property,
643 HeapVector<Member<Element>>& result) const {
644 Element* element = this->GetElement();
645 if (!element)
646 return false;
647
648 return AccessibleNode::GetPropertyOrARIAAttribute(element, property, result);
649 }
650
HasAOMPropertyOrARIAAttribute(AOMBooleanProperty property,bool & result) const651 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMBooleanProperty property,
652 bool& result) const {
653 Element* element = this->GetElement();
654 if (!element)
655 return false;
656
657 bool is_null = true;
658 result =
659 AccessibleNode::GetPropertyOrARIAAttribute(element, property, is_null);
660 return !is_null;
661 }
662
AOMPropertyOrARIAAttributeIsTrue(AOMBooleanProperty property) const663 bool AXObject::AOMPropertyOrARIAAttributeIsTrue(
664 AOMBooleanProperty property) const {
665 bool result;
666 if (HasAOMPropertyOrARIAAttribute(property, result))
667 return result;
668 return false;
669 }
670
AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty property) const671 bool AXObject::AOMPropertyOrARIAAttributeIsFalse(
672 AOMBooleanProperty property) const {
673 bool result;
674 if (HasAOMPropertyOrARIAAttribute(property, result))
675 return !result;
676 return false;
677 }
678
HasAOMPropertyOrARIAAttribute(AOMUIntProperty property,uint32_t & result) const679 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMUIntProperty property,
680 uint32_t& result) const {
681 Element* element = this->GetElement();
682 if (!element)
683 return false;
684
685 bool is_null = true;
686 result =
687 AccessibleNode::GetPropertyOrARIAAttribute(element, property, is_null);
688 return !is_null;
689 }
690
HasAOMPropertyOrARIAAttribute(AOMIntProperty property,int32_t & result) const691 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMIntProperty property,
692 int32_t& result) const {
693 Element* element = this->GetElement();
694 if (!element)
695 return false;
696
697 bool is_null = true;
698 result =
699 AccessibleNode::GetPropertyOrARIAAttribute(element, property, is_null);
700 return !is_null;
701 }
702
HasAOMPropertyOrARIAAttribute(AOMFloatProperty property,float & result) const703 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMFloatProperty property,
704 float& result) const {
705 Element* element = this->GetElement();
706 if (!element)
707 return false;
708
709 bool is_null = true;
710 result =
711 AccessibleNode::GetPropertyOrARIAAttribute(element, property, is_null);
712 return !is_null;
713 }
714
HasAOMPropertyOrARIAAttribute(AOMStringProperty property,AtomicString & result) const715 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMStringProperty property,
716 AtomicString& result) const {
717 Element* element = this->GetElement();
718 if (!element)
719 return false;
720
721 result = AccessibleNode::GetPropertyOrARIAAttribute(element, property);
722 return !result.IsNull();
723 }
724
GetAccessibleNode() const725 AccessibleNode* AXObject::GetAccessibleNode() const {
726 Element* element = GetElement();
727 if (!element)
728 return nullptr;
729
730 return element->ExistingAccessibleNode();
731 }
732
GetSparseAXAttributes(AXSparseAttributeClient & sparse_attribute_client) const733 void AXObject::GetSparseAXAttributes(
734 AXSparseAttributeClient& sparse_attribute_client) const {
735 AXSparseAttributeAOMPropertyClient property_client(*ax_object_cache_,
736 sparse_attribute_client);
737 AccessibleNode* accessible_node = GetAccessibleNode();
738
739 // Virtual nodes for AOM are still tied to the AXTree.
740 if (accessible_node && IsVirtualObject())
741 accessible_node->GetAllAOMProperties(&property_client);
742
743 Element* element = GetElement();
744 if (!element)
745 return;
746
747 AXSparseAttributeSetterMap& ax_sparse_attribute_setter_map =
748 GetSparseAttributeSetterMap();
749 AttributeCollection attributes = element->AttributesWithoutUpdate();
750 HashSet<QualifiedName> set_attributes;
751 for (const Attribute& attr : attributes) {
752 set_attributes.insert(attr.GetName());
753
754 AXSparseAttributeSetter* setter =
755 ax_sparse_attribute_setter_map.at(attr.GetName());
756 if (setter)
757 setter->Run(*this, sparse_attribute_client, attr.Value());
758 }
759 if (!element->DidAttachInternals())
760 return;
761 const auto& internals_attributes =
762 element->EnsureElementInternals().GetAttributes();
763 for (const QualifiedName& attr : internals_attributes.Keys()) {
764 if (set_attributes.Contains(attr))
765 continue;
766 AXSparseAttributeSetter* setter = ax_sparse_attribute_setter_map.at(attr);
767 if (setter) {
768 setter->Run(*this, sparse_attribute_client,
769 internals_attributes.at(attr));
770 }
771 }
772 }
773
Serialize(ui::AXNodeData * node_data,ui::AXMode accessibility_mode)774 void AXObject::Serialize(ui::AXNodeData* node_data,
775 ui::AXMode accessibility_mode) {
776 AccessibilityExpanded expanded = IsExpanded();
777 if (expanded) {
778 if (expanded == kExpandedCollapsed)
779 node_data->AddState(ax::mojom::blink::State::kCollapsed);
780 else if (expanded == kExpandedExpanded)
781 node_data->AddState(ax::mojom::blink::State::kExpanded);
782 }
783
784 if (CanSetFocusAttribute())
785 node_data->AddState(ax::mojom::blink::State::kFocusable);
786
787 if (HasPopup() != ax::mojom::blink::HasPopup::kFalse)
788 node_data->SetHasPopup(HasPopup());
789 else if (RoleValue() == ax::mojom::blink::Role::kPopUpButton)
790 node_data->SetHasPopup(ax::mojom::blink::HasPopup::kMenu);
791
792 if (IsAutofillAvailable())
793 node_data->AddState(ax::mojom::blink::State::kAutofillAvailable);
794
795 if (IsDefault())
796 node_data->AddState(ax::mojom::blink::State::kDefault);
797
798 // aria-grabbed is deprecated in WAI-ARIA 1.1.
799 if (IsGrabbed() != kGrabbedStateUndefined) {
800 node_data->AddBoolAttribute(ax::mojom::blink::BoolAttribute::kGrabbed,
801 IsGrabbed() == kGrabbedStateTrue);
802 }
803
804 if (IsHovered())
805 node_data->AddState(ax::mojom::blink::State::kHovered);
806
807 if (!IsVisible())
808 node_data->AddState(ax::mojom::blink::State::kInvisible);
809
810 if (IsLinked())
811 node_data->AddState(ax::mojom::blink::State::kLinked);
812
813 if (IsMultiline())
814 node_data->AddState(ax::mojom::blink::State::kMultiline);
815
816 if (IsMultiSelectable())
817 node_data->AddState(ax::mojom::blink::State::kMultiselectable);
818
819 if (IsPasswordField())
820 node_data->AddState(ax::mojom::blink::State::kProtected);
821
822 if (IsRequired())
823 node_data->AddState(ax::mojom::blink::State::kRequired);
824
825 if (IsEditable())
826 node_data->AddState(ax::mojom::blink::State::kEditable);
827
828 if (IsSelected() != blink::kSelectedStateUndefined) {
829 node_data->AddBoolAttribute(ax::mojom::blink::BoolAttribute::kSelected,
830 IsSelected() == blink::kSelectedStateTrue);
831 node_data->AddBoolAttribute(
832 ax::mojom::blink::BoolAttribute::kSelectedFromFocus,
833 IsSelectedFromFocus());
834 }
835
836 if (IsNotUserSelectable()) {
837 node_data->AddBoolAttribute(
838 ax::mojom::blink::BoolAttribute::kNotUserSelectableStyle, true);
839 }
840
841 if (IsRichlyEditable())
842 node_data->AddState(ax::mojom::blink::State::kRichlyEditable);
843
844 if (IsVisited())
845 node_data->AddState(ax::mojom::blink::State::kVisited);
846
847 if (Orientation() == kAccessibilityOrientationVertical)
848 node_data->AddState(ax::mojom::blink::State::kVertical);
849 else if (Orientation() == blink::kAccessibilityOrientationHorizontal)
850 node_data->AddState(ax::mojom::blink::State::kHorizontal);
851
852 if (AccessibilityIsIgnored())
853 node_data->AddState(ax::mojom::blink::State::kIgnored);
854
855 if (GetTextAlign() != ax::mojom::blink::TextAlign::kNone) {
856 node_data->SetTextAlign(GetTextAlign());
857 }
858
859 if (GetTextIndent() != 0.0f) {
860 node_data->AddFloatAttribute(ax::mojom::blink::FloatAttribute::kTextIndent,
861 GetTextIndent());
862 }
863
864 // If this is an HTMLFrameOwnerElement (such as an iframe), we may need
865 // to embed the ID of the child frame.
866 if (auto* html_frame_owner_element =
867 DynamicTo<HTMLFrameOwnerElement>(GetElement())) {
868 if (Frame* child_frame = html_frame_owner_element->ContentFrame()) {
869 base::Optional<base::UnguessableToken> child_token =
870 child_frame->GetEmbeddingToken();
871 if (child_token && !(IsDetached() || ChildCountIncludingIgnored())) {
872 node_data->AddStringAttribute(
873 ax::mojom::blink::StringAttribute::kChildTreeId,
874 child_token->ToString());
875 }
876 }
877 }
878
879 if (accessibility_mode.has_mode(ui::AXMode::kScreenReader) ||
880 accessibility_mode.has_mode(ui::AXMode::kPDF)) {
881 // The DOMNodeID from Blink. Currently only populated when using
882 // the accessibility tree for PDF exporting. Warning, this is totally
883 // unrelated to the accessibility node ID, or the ID attribute for an
884 // HTML element - it's an ID used to uniquely identify nodes in Blink.
885 int dom_node_id = GetDOMNodeId();
886 if (dom_node_id) {
887 node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kDOMNodeId,
888 dom_node_id);
889 }
890
891 SerializeTableAttributes(node_data);
892 }
893
894 if (accessibility_mode.has_mode(ui::AXMode::kPDF)) {
895 // Return early. None of the following attributes are needed for PDFs.
896 return;
897 }
898
899 if (ValueDescription().length()) {
900 TruncateAndAddStringAttribute(node_data,
901 ax::mojom::blink::StringAttribute::kValue,
902 ValueDescription().Utf8());
903 } else {
904 TruncateAndAddStringAttribute(node_data,
905 ax::mojom::blink::StringAttribute::kValue,
906 StringValue().Utf8());
907 }
908
909 switch (Restriction()) {
910 case AXRestriction::kRestrictionReadOnly:
911 node_data->SetRestriction(ax::mojom::blink::Restriction::kReadOnly);
912 break;
913 case AXRestriction::kRestrictionDisabled:
914 node_data->SetRestriction(ax::mojom::blink::Restriction::kDisabled);
915 break;
916 case AXRestriction::kRestrictionNone:
917 if (CanSetValueAttribute())
918 node_data->AddAction(ax::mojom::blink::Action::kSetValue);
919 break;
920 }
921
922 if (!Url().IsEmpty()) {
923 TruncateAndAddStringAttribute(node_data,
924 ax::mojom::blink::StringAttribute::kUrl,
925 Url().GetString().Utf8());
926 }
927
928 if (accessibility_mode.has_mode(ui::AXMode::kScreenReader)) {
929 SerializeStyleAttributes(node_data);
930 }
931
932 SerializePartialSparseAttributes(node_data);
933
934 if (Element* element = this->GetElement()) {
935 if (const AtomicString& class_name = element->GetClassAttribute()) {
936 TruncateAndAddStringAttribute(
937 node_data, ax::mojom::blink::StringAttribute::kClassName,
938 class_name.Utf8());
939 }
940
941 if (const AtomicString& aria_role =
942 GetAOMPropertyOrARIAAttribute(AOMStringProperty::kRole)) {
943 TruncateAndAddStringAttribute(node_data,
944 ax::mojom::blink::StringAttribute::kRole,
945 aria_role.Utf8());
946 } else {
947 std::string role_str = GetEquivalentAriaRoleString(RoleValue());
948 if (!role_str.empty()) {
949 TruncateAndAddStringAttribute(node_data,
950 ax::mojom::blink::StringAttribute::kRole,
951 GetEquivalentAriaRoleString(RoleValue()));
952 }
953 }
954
955 if (IsEditable()) {
956 if (IsEditableRoot()) {
957 node_data->AddBoolAttribute(
958 ax::mojom::blink::BoolAttribute::kEditableRoot, true);
959 }
960
961 if (IsNativeTextControl()) {
962 // Selection offsets are only used for plain text controls, (input of a
963 // text field type, and textarea). Rich editable areas, such as
964 // contenteditables, use AXTreeData.
965 //
966 // TODO(nektar): Remove kTextSelStart and kTextSelEnd from the renderer.
967 const auto ax_selection =
968 AXSelection::FromCurrentSelection(ToTextControl(*element));
969 int start = ax_selection.Base().IsTextPosition()
970 ? ax_selection.Base().TextOffset()
971 : ax_selection.Base().ChildIndex();
972 int end = ax_selection.Extent().IsTextPosition()
973 ? ax_selection.Extent().TextOffset()
974 : ax_selection.Extent().ChildIndex();
975 node_data->AddIntAttribute(
976 ax::mojom::blink::IntAttribute::kTextSelStart, start);
977 node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kTextSelEnd,
978 end);
979 }
980 }
981 }
982 }
983
SerializeTableAttributes(ui::AXNodeData * node_data)984 void AXObject::SerializeTableAttributes(ui::AXNodeData* node_data) {
985 if (ui::IsTableLike(RoleValue())) {
986 int aria_colcount = AriaColumnCount();
987 if (aria_colcount) {
988 node_data->AddIntAttribute(
989 ax::mojom::blink::IntAttribute::kAriaColumnCount, aria_colcount);
990 }
991 int aria_rowcount = AriaRowCount();
992 if (aria_rowcount) {
993 node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kAriaRowCount,
994 aria_rowcount);
995 }
996 }
997
998 if (ui::IsTableRow(RoleValue())) {
999 AXObject* header = HeaderObject();
1000 if (header && !header->IsDetached()) {
1001 // TODO(accessibility): these should be computed by ui::AXTableInfo and
1002 // removed here.
1003 node_data->AddIntAttribute(
1004 ax::mojom::blink::IntAttribute::kTableRowHeaderId,
1005 header->AXObjectID());
1006 }
1007 }
1008
1009 if (ui::IsCellOrTableHeader(RoleValue())) {
1010 node_data->AddIntAttribute(
1011 ax::mojom::blink::IntAttribute::kTableCellColumnSpan, ColumnSpan());
1012 node_data->AddIntAttribute(
1013 ax::mojom::blink::IntAttribute::kTableCellRowSpan, RowSpan());
1014 }
1015
1016 if (ui::IsCellOrTableHeader(RoleValue()) || ui::IsTableRow(RoleValue())) {
1017 // aria-rowindex and aria-colindex are supported on cells, headers and
1018 // rows.
1019 int aria_rowindex = AriaRowIndex();
1020 if (aria_rowindex) {
1021 node_data->AddIntAttribute(
1022 ax::mojom::blink::IntAttribute::kAriaCellRowIndex, aria_rowindex);
1023 }
1024
1025 int aria_colindex = AriaColumnIndex();
1026 if (aria_colindex) {
1027 node_data->AddIntAttribute(
1028 ax::mojom::blink::IntAttribute::kAriaCellColumnIndex, aria_colindex);
1029 }
1030 }
1031
1032 if (ui::IsTableHeader(RoleValue()) &&
1033 GetSortDirection() != ax::mojom::blink::SortDirection::kNone) {
1034 node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kSortDirection,
1035 static_cast<int32_t>(GetSortDirection()));
1036 }
1037 }
1038
SerializeStyleAttributes(ui::AXNodeData * node_data)1039 void AXObject::SerializeStyleAttributes(ui::AXNodeData* node_data) {
1040 // Text attributes.
1041 if (BackgroundColor()) {
1042 node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kBackgroundColor,
1043 BackgroundColor());
1044 }
1045
1046 if (GetColor()) {
1047 node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kColor,
1048 GetColor());
1049 }
1050
1051 AXObject* parent = ParentObjectUnignored();
1052 if (FontFamily().length()) {
1053 if (!parent || parent->FontFamily() != FontFamily()) {
1054 TruncateAndAddStringAttribute(
1055 node_data, ax::mojom::blink::StringAttribute::kFontFamily,
1056 FontFamily().Utf8());
1057 }
1058 }
1059
1060 // Font size is in pixels.
1061 if (FontSize()) {
1062 node_data->AddFloatAttribute(ax::mojom::blink::FloatAttribute::kFontSize,
1063 FontSize());
1064 }
1065
1066 if (FontWeight()) {
1067 node_data->AddFloatAttribute(ax::mojom::blink::FloatAttribute::kFontWeight,
1068 FontWeight());
1069 }
1070
1071 if (RoleValue() == ax::mojom::blink::Role::kListItem &&
1072 GetListStyle() != ax::mojom::blink::ListStyle::kNone) {
1073 node_data->SetListStyle(GetListStyle());
1074 }
1075
1076 if (GetTextDirection() != ax::mojom::blink::WritingDirection::kNone) {
1077 node_data->SetTextDirection(GetTextDirection());
1078 }
1079
1080 if (GetTextPosition() != ax::mojom::blink::TextPosition::kNone) {
1081 node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kTextPosition,
1082 static_cast<int32_t>(GetTextPosition()));
1083 }
1084
1085 int32_t text_style = 0;
1086 ax::mojom::blink::TextDecorationStyle text_overline_style;
1087 ax::mojom::blink::TextDecorationStyle text_strikethrough_style;
1088 ax::mojom::blink::TextDecorationStyle text_underline_style;
1089 GetTextStyleAndTextDecorationStyle(&text_style, &text_overline_style,
1090 &text_strikethrough_style,
1091 &text_underline_style);
1092 if (text_style) {
1093 node_data->AddIntAttribute(ax::mojom::blink::IntAttribute::kTextStyle,
1094 text_style);
1095 }
1096
1097 if (text_overline_style != ax::mojom::blink::TextDecorationStyle::kNone) {
1098 node_data->AddIntAttribute(
1099 ax::mojom::blink::IntAttribute::kTextOverlineStyle,
1100 static_cast<int32_t>(text_overline_style));
1101 }
1102
1103 if (text_strikethrough_style !=
1104 ax::mojom::blink::TextDecorationStyle::kNone) {
1105 node_data->AddIntAttribute(
1106 ax::mojom::blink::IntAttribute::kTextStrikethroughStyle,
1107 static_cast<int32_t>(text_strikethrough_style));
1108 }
1109
1110 if (text_underline_style != ax::mojom::blink::TextDecorationStyle::kNone) {
1111 node_data->AddIntAttribute(
1112 ax::mojom::blink::IntAttribute::kTextUnderlineStyle,
1113 static_cast<int32_t>(text_underline_style));
1114 }
1115 }
1116
SerializePartialSparseAttributes(ui::AXNodeData * node_data)1117 void AXObject::SerializePartialSparseAttributes(ui::AXNodeData* node_data) {
1118 if (IsVirtualObject()) {
1119 AccessibleNode* accessible_node = GetAccessibleNode();
1120 if (accessible_node) {
1121 AXNodeDataAOMPropertyClient property_client(*ax_object_cache_,
1122 *node_data);
1123 accessible_node->GetAllAOMProperties(&property_client);
1124 }
1125 }
1126
1127 Element* element = GetElement();
1128 if (!element)
1129 return;
1130
1131 TempSetterMap& setter_map = GetTempSetterMap();
1132 AttributeCollection attributes = element->AttributesWithoutUpdate();
1133 HashSet<QualifiedName> set_attributes;
1134 for (const Attribute& attr : attributes) {
1135 set_attributes.insert(attr.GetName());
1136 AXSparseSetterFunc callback = setter_map.at(attr.GetName());
1137
1138 if (callback)
1139 callback.Run(this, node_data, attr.Value());
1140 }
1141
1142 if (!element->DidAttachInternals())
1143 return;
1144 const auto& internals_attributes =
1145 element->EnsureElementInternals().GetAttributes();
1146 for (const QualifiedName& attr : internals_attributes.Keys()) {
1147 if (set_attributes.Contains(attr))
1148 continue;
1149
1150 AXSparseSetterFunc callback = setter_map.at(attr);
1151
1152 if (callback)
1153 callback.Run(this, node_data, internals_attributes.at(attr));
1154 }
1155 }
1156
TruncateAndAddStringAttribute(ui::AXNodeData * dst,ax::mojom::blink::StringAttribute attribute,const std::string & value,uint32_t max_len) const1157 void AXObject::TruncateAndAddStringAttribute(
1158 ui::AXNodeData* dst,
1159 ax::mojom::blink::StringAttribute attribute,
1160 const std::string& value,
1161 uint32_t max_len) const {
1162 if (value.size() > max_len) {
1163 std::string truncated;
1164 base::TruncateUTF8ToByteSize(value, max_len, &truncated);
1165 dst->AddStringAttribute(attribute, truncated);
1166 } else {
1167 dst->AddStringAttribute(attribute, value);
1168 }
1169 }
1170
IsAXNodeObject() const1171 bool AXObject::IsAXNodeObject() const {
1172 return false;
1173 }
1174
IsAXLayoutObject() const1175 bool AXObject::IsAXLayoutObject() const {
1176 return false;
1177 }
1178
IsAXInlineTextBox() const1179 bool AXObject::IsAXInlineTextBox() const {
1180 return false;
1181 }
1182
IsList() const1183 bool AXObject::IsList() const {
1184 return ui::IsList(RoleValue());
1185 }
1186
IsAXListBox() const1187 bool AXObject::IsAXListBox() const {
1188 return false;
1189 }
1190
IsAXListBoxOption() const1191 bool AXObject::IsAXListBoxOption() const {
1192 return false;
1193 }
1194
IsMenuList() const1195 bool AXObject::IsMenuList() const {
1196 return false;
1197 }
1198
IsMenuListOption() const1199 bool AXObject::IsMenuListOption() const {
1200 return false;
1201 }
1202
IsMenuListPopup() const1203 bool AXObject::IsMenuListPopup() const {
1204 return false;
1205 }
1206
IsMockObject() const1207 bool AXObject::IsMockObject() const {
1208 return false;
1209 }
1210
IsProgressIndicator() const1211 bool AXObject::IsProgressIndicator() const {
1212 return false;
1213 }
1214
IsAXRadioInput() const1215 bool AXObject::IsAXRadioInput() const {
1216 return false;
1217 }
1218
IsSlider() const1219 bool AXObject::IsSlider() const {
1220 return false;
1221 }
1222
IsAXSVGRoot() const1223 bool AXObject::IsAXSVGRoot() const {
1224 return false;
1225 }
1226
IsValidationMessage() const1227 bool AXObject::IsValidationMessage() const {
1228 return false;
1229 }
1230
IsVirtualObject() const1231 bool AXObject::IsVirtualObject() const {
1232 return false;
1233 }
1234
RoleValue() const1235 ax::mojom::blink::Role AXObject::RoleValue() const {
1236 return role_;
1237 }
1238
IsARIATextControl() const1239 bool AXObject::IsARIATextControl() const {
1240 return AriaRoleAttribute() == ax::mojom::blink::Role::kTextField ||
1241 AriaRoleAttribute() == ax::mojom::blink::Role::kSearchBox ||
1242 AriaRoleAttribute() == ax::mojom::blink::Role::kTextFieldWithComboBox;
1243 }
1244
IsAnchor() const1245 bool AXObject::IsAnchor() const {
1246 return IsLink() && !IsNativeImage();
1247 }
1248
IsButton() const1249 bool AXObject::IsButton() const {
1250 return ui::IsButton(RoleValue());
1251 }
1252
IsCanvas() const1253 bool AXObject::IsCanvas() const {
1254 return RoleValue() == ax::mojom::blink::Role::kCanvas;
1255 }
1256
IsCheckbox() const1257 bool AXObject::IsCheckbox() const {
1258 return RoleValue() == ax::mojom::blink::Role::kCheckBox;
1259 }
1260
IsCheckboxOrRadio() const1261 bool AXObject::IsCheckboxOrRadio() const {
1262 return IsCheckbox() || IsRadioButton();
1263 }
1264
IsColorWell() const1265 bool AXObject::IsColorWell() const {
1266 return RoleValue() == ax::mojom::blink::Role::kColorWell;
1267 }
1268
IsControl() const1269 bool AXObject::IsControl() const {
1270 return ui::IsControl(RoleValue());
1271 }
1272
IsDefault() const1273 bool AXObject::IsDefault() const {
1274 return false;
1275 }
1276
IsFieldset() const1277 bool AXObject::IsFieldset() const {
1278 return false;
1279 }
1280
IsHeading() const1281 bool AXObject::IsHeading() const {
1282 return ui::IsHeading(RoleValue());
1283 }
1284
IsImage() const1285 bool AXObject::IsImage() const {
1286 // Canvas is not currently included so that it is not exposed unless there is
1287 // a label, fallback content or something to make it accessible. This decision
1288 // may be revisited at a later date.
1289 return ui::IsImage(RoleValue()) &&
1290 RoleValue() != ax::mojom::blink::Role::kCanvas;
1291 }
1292
IsInputImage() const1293 bool AXObject::IsInputImage() const {
1294 return false;
1295 }
1296
IsLink() const1297 bool AXObject::IsLink() const {
1298 return ui::IsLink(RoleValue());
1299 }
1300
IsInPageLinkTarget() const1301 bool AXObject::IsInPageLinkTarget() const {
1302 return false;
1303 }
1304
IsImageMapLink() const1305 bool AXObject::IsImageMapLink() const {
1306 return false;
1307 }
1308
IsMenu() const1309 bool AXObject::IsMenu() const {
1310 return RoleValue() == ax::mojom::blink::Role::kMenu;
1311 }
1312
IsCheckable() const1313 bool AXObject::IsCheckable() const {
1314 switch (RoleValue()) {
1315 case ax::mojom::blink::Role::kCheckBox:
1316 case ax::mojom::blink::Role::kMenuItemCheckBox:
1317 case ax::mojom::blink::Role::kMenuItemRadio:
1318 case ax::mojom::blink::Role::kRadioButton:
1319 case ax::mojom::blink::Role::kSwitch:
1320 case ax::mojom::blink::Role::kToggleButton:
1321 return true;
1322 case ax::mojom::blink::Role::kTreeItem:
1323 case ax::mojom::blink::Role::kListBoxOption:
1324 case ax::mojom::blink::Role::kMenuListOption:
1325 return AriaCheckedIsPresent();
1326 default:
1327 return false;
1328 }
1329 }
1330
1331 // Why this is here instead of AXNodeObject:
1332 // Because an AXMenuListOption (<option>) can
1333 // have an ARIA role of menuitemcheckbox/menuitemradio
1334 // yet does not inherit from AXNodeObject
CheckedState() const1335 ax::mojom::blink::CheckedState AXObject::CheckedState() const {
1336 if (!IsCheckable())
1337 return ax::mojom::blink::CheckedState::kNone;
1338
1339 // Try ARIA checked/pressed state
1340 const ax::mojom::blink::Role role = RoleValue();
1341 const auto prop = role == ax::mojom::blink::Role::kToggleButton
1342 ? AOMStringProperty::kPressed
1343 : AOMStringProperty::kChecked;
1344 const AtomicString& checked_attribute = GetAOMPropertyOrARIAAttribute(prop);
1345 if (checked_attribute) {
1346 if (EqualIgnoringASCIICase(checked_attribute, "mixed")) {
1347 // Only checkable role that doesn't support mixed is the switch.
1348 if (role != ax::mojom::blink::Role::kSwitch)
1349 return ax::mojom::blink::CheckedState::kMixed;
1350 }
1351
1352 // Anything other than "false" should be treated as "true".
1353 return EqualIgnoringASCIICase(checked_attribute, "false")
1354 ? ax::mojom::blink::CheckedState::kFalse
1355 : ax::mojom::blink::CheckedState::kTrue;
1356 }
1357
1358 // Native checked state
1359 if (role != ax::mojom::blink::Role::kToggleButton) {
1360 const Node* node = this->GetNode();
1361 if (!node)
1362 return ax::mojom::blink::CheckedState::kNone;
1363
1364 // Expose native checkbox mixed state as accessibility mixed state. However,
1365 // do not expose native radio mixed state as accessibility mixed state.
1366 // This would confuse the JAWS screen reader, which reports a mixed radio as
1367 // both checked and partially checked, but a native mixed native radio
1368 // button sinply means no radio buttons have been checked in the group yet.
1369 if (IsNativeCheckboxInMixedState(node))
1370 return ax::mojom::blink::CheckedState::kMixed;
1371
1372 auto* html_input_element = DynamicTo<HTMLInputElement>(node);
1373 if (html_input_element && html_input_element->ShouldAppearChecked()) {
1374 return ax::mojom::blink::CheckedState::kTrue;
1375 }
1376 }
1377
1378 return ax::mojom::blink::CheckedState::kFalse;
1379 }
1380
IsNativeCheckboxInMixedState(const Node * node)1381 bool AXObject::IsNativeCheckboxInMixedState(const Node* node) {
1382 const auto* input = DynamicTo<HTMLInputElement>(node);
1383 if (!input)
1384 return false;
1385
1386 const auto inputType = input->type();
1387 if (inputType != input_type_names::kCheckbox)
1388 return false;
1389 return input->ShouldAppearIndeterminate();
1390 }
1391
IsLandmarkRelated() const1392 bool AXObject::IsLandmarkRelated() const {
1393 switch (RoleValue()) {
1394 case ax::mojom::blink::Role::kApplication:
1395 case ax::mojom::blink::Role::kArticle:
1396 case ax::mojom::blink::Role::kBanner:
1397 case ax::mojom::blink::Role::kComplementary:
1398 case ax::mojom::blink::Role::kContentInfo:
1399 case ax::mojom::blink::Role::kDocAcknowledgments:
1400 case ax::mojom::blink::Role::kDocAfterword:
1401 case ax::mojom::blink::Role::kDocAppendix:
1402 case ax::mojom::blink::Role::kDocBibliography:
1403 case ax::mojom::blink::Role::kDocChapter:
1404 case ax::mojom::blink::Role::kDocConclusion:
1405 case ax::mojom::blink::Role::kDocCredits:
1406 case ax::mojom::blink::Role::kDocEndnotes:
1407 case ax::mojom::blink::Role::kDocEpilogue:
1408 case ax::mojom::blink::Role::kDocErrata:
1409 case ax::mojom::blink::Role::kDocForeword:
1410 case ax::mojom::blink::Role::kDocGlossary:
1411 case ax::mojom::blink::Role::kDocIntroduction:
1412 case ax::mojom::blink::Role::kDocPart:
1413 case ax::mojom::blink::Role::kDocPreface:
1414 case ax::mojom::blink::Role::kDocPrologue:
1415 case ax::mojom::blink::Role::kDocToc:
1416 case ax::mojom::blink::Role::kFooter:
1417 case ax::mojom::blink::Role::kForm:
1418 case ax::mojom::blink::Role::kHeader:
1419 case ax::mojom::blink::Role::kMain:
1420 case ax::mojom::blink::Role::kNavigation:
1421 case ax::mojom::blink::Role::kRegion:
1422 case ax::mojom::blink::Role::kSearch:
1423 case ax::mojom::blink::Role::kSection:
1424 return true;
1425 default:
1426 return false;
1427 }
1428 }
1429
IsMenuRelated() const1430 bool AXObject::IsMenuRelated() const {
1431 return ui::IsMenuRelated(RoleValue());
1432 }
1433
IsMeter() const1434 bool AXObject::IsMeter() const {
1435 return RoleValue() == ax::mojom::blink::Role::kMeter;
1436 }
1437
IsNativeImage() const1438 bool AXObject::IsNativeImage() const {
1439 return false;
1440 }
1441
IsNativeSpinButton() const1442 bool AXObject::IsNativeSpinButton() const {
1443 return false;
1444 }
1445
IsNativeTextControl() const1446 bool AXObject::IsNativeTextControl() const {
1447 return false;
1448 }
1449
IsNonNativeTextControl() const1450 bool AXObject::IsNonNativeTextControl() const {
1451 return false;
1452 }
1453
IsPasswordField() const1454 bool AXObject::IsPasswordField() const {
1455 return false;
1456 }
1457
IsPasswordFieldAndShouldHideValue() const1458 bool AXObject::IsPasswordFieldAndShouldHideValue() const {
1459 Settings* settings = GetDocument()->GetSettings();
1460 if (!settings || settings->GetAccessibilityPasswordValuesEnabled())
1461 return false;
1462
1463 return IsPasswordField();
1464 }
1465
IsPresentational() const1466 bool AXObject::IsPresentational() const {
1467 return ui::IsPresentational(RoleValue());
1468 }
1469
IsTextObject() const1470 bool AXObject::IsTextObject() const {
1471 // Objects with |ax::mojom::blink::Role::kLineBreak| are HTML <br> elements
1472 // and are not backed by DOM text nodes. We can't mark them as text objects
1473 // for that reason.
1474 switch (RoleValue()) {
1475 case ax::mojom::blink::Role::kInlineTextBox:
1476 case ax::mojom::blink::Role::kStaticText:
1477 return true;
1478 default:
1479 return false;
1480 }
1481 }
1482
IsRangeValueSupported() const1483 bool AXObject::IsRangeValueSupported() const {
1484 if (RoleValue() == ax::mojom::blink::Role::kSplitter) {
1485 // According to the ARIA spec, role="separator" acts as a splitter only
1486 // when focusable, and supports a range only in that case.
1487 return CanSetFocusAttribute();
1488 }
1489 return ui::IsRangeValueSupported(RoleValue());
1490 }
1491
IsClickable() const1492 bool AXObject::IsClickable() const {
1493 return ui::IsClickable(RoleValue());
1494 }
1495
AccessibilityIsIgnored() const1496 bool AXObject::AccessibilityIsIgnored() const {
1497 UpdateDistributionForFlatTreeTraversal();
1498 UpdateCachedAttributeValuesIfNeeded();
1499 return cached_is_ignored_;
1500 }
1501
AccessibilityIsIgnoredButIncludedInTree() const1502 bool AXObject::AccessibilityIsIgnoredButIncludedInTree() const {
1503 UpdateDistributionForFlatTreeTraversal();
1504 UpdateCachedAttributeValuesIfNeeded();
1505 return cached_is_ignored_but_included_in_tree_;
1506 }
1507
1508 // AccessibilityIsIncludedInTree should be true for all nodes that should be
1509 // included in the tree, even if they are ignored
AccessibilityIsIncludedInTree() const1510 bool AXObject::AccessibilityIsIncludedInTree() const {
1511 return !AccessibilityIsIgnored() || AccessibilityIsIgnoredButIncludedInTree();
1512 }
1513
UpdateCachedAttributeValuesIfNeeded() const1514 void AXObject::UpdateCachedAttributeValuesIfNeeded() const {
1515 if (IsDetached())
1516 return;
1517
1518 AXObjectCacheImpl& cache = AXObjectCache();
1519
1520 if (cache.ModificationCount() == last_modification_count_)
1521 return;
1522
1523 #if DCHECK_IS_ON() // Required in order to get Lifecycle().ToString()
1524 DCHECK(!GetDocument() || GetDocument()->Lifecycle().GetState() >=
1525 DocumentLifecycle::kAfterPerformLayout)
1526 << "Unclean document at lifecycle "
1527 << GetDocument()->Lifecycle().ToString();
1528 #endif
1529
1530 last_modification_count_ = cache.ModificationCount();
1531
1532 cached_background_color_ = ComputeBackgroundColor();
1533 // TODO(aleventhal) Temporary crash fix until CL:2485519 lands.
1534 if (IsDetached())
1535 return;
1536
1537 cached_is_hidden_via_style = ComputeIsHiddenViaStyle();
1538 cached_is_inert_or_aria_hidden_ = ComputeIsInertOrAriaHidden();
1539 cached_is_descendant_of_leaf_node_ = !!LeafNodeAncestor();
1540 cached_is_descendant_of_disabled_node_ = !!DisabledAncestor();
1541 cached_has_inherited_presentational_role_ =
1542 !!InheritsPresentationalRoleFrom();
1543 cached_is_ignored_ = ComputeAccessibilityIsIgnored();
1544 cached_is_ignored_but_included_in_tree_ =
1545 cached_is_ignored_ && ComputeAccessibilityIsIgnoredButIncludedInTree();
1546 cached_is_editable_root_ = ComputeIsEditableRoot();
1547 // Compute live region root, which can be from any ARIA live value, including
1548 // "off", or from an automatic ARIA live value, e.g. from role="status".
1549 // TODO(dmazzoni): remove this const_cast.
1550 AtomicString aria_live;
1551 cached_live_region_root_ =
1552 IsLiveRegionRoot()
1553 ? const_cast<AXObject*>(this)
1554 : (ParentObjectIfExists() ? ParentObjectIfExists()->LiveRegionRoot()
1555 : nullptr);
1556 cached_aria_column_index_ = ComputeAriaColumnIndex();
1557 cached_aria_row_index_ = ComputeAriaRowIndex();
1558
1559 bool ignored_states_changed = false;
1560 if (cached_is_ignored_ != LastKnownIsIgnoredValue()) {
1561 last_known_is_ignored_value_ =
1562 cached_is_ignored_ ? kIgnoreObject : kIncludeObject;
1563 ignored_states_changed = true;
1564 }
1565
1566 if (cached_is_ignored_but_included_in_tree_ !=
1567 LastKnownIsIgnoredButIncludedInTreeValue()) {
1568 last_known_is_ignored_but_included_in_tree_value_ =
1569 cached_is_ignored_but_included_in_tree_ ? kIncludeObject
1570 : kIgnoreObject;
1571 ignored_states_changed = true;
1572 }
1573
1574 if (ignored_states_changed) {
1575 if (AXObject* parent = ParentObjectIfExists())
1576 parent->ChildrenChanged();
1577 }
1578
1579 if (GetLayoutObject() && GetLayoutObject()->IsText()) {
1580 cached_local_bounding_box_rect_for_accessibility_ =
1581 GetLayoutObject()->LocalBoundingBoxRectForAccessibility();
1582 }
1583 }
1584
AccessibilityIsIgnoredByDefault(IgnoredReasons * ignored_reasons) const1585 bool AXObject::AccessibilityIsIgnoredByDefault(
1586 IgnoredReasons* ignored_reasons) const {
1587 return DefaultObjectInclusion(ignored_reasons) == kIgnoreObject;
1588 }
1589
DefaultObjectInclusion(IgnoredReasons * ignored_reasons) const1590 AXObjectInclusion AXObject::DefaultObjectInclusion(
1591 IgnoredReasons* ignored_reasons) const {
1592 if (IsInertOrAriaHidden()) {
1593 // Keep focusable elements that are aria-hidden in tree, so that they can
1594 // still fire events such as focus and value changes.
1595 if (!CanSetFocusAttribute()) {
1596 if (ignored_reasons)
1597 ComputeIsInertOrAriaHidden(ignored_reasons);
1598 return kIgnoreObject;
1599 }
1600 }
1601
1602 return kDefaultBehavior;
1603 }
1604
IsInertOrAriaHidden() const1605 bool AXObject::IsInertOrAriaHidden() const {
1606 UpdateCachedAttributeValuesIfNeeded();
1607 return cached_is_inert_or_aria_hidden_;
1608 }
1609
ComputeIsInertOrAriaHidden(IgnoredReasons * ignored_reasons) const1610 bool AXObject::ComputeIsInertOrAriaHidden(
1611 IgnoredReasons* ignored_reasons) const {
1612 if (GetNode()) {
1613 if (GetNode()->IsInert()) {
1614 if (ignored_reasons) {
1615 HTMLDialogElement* dialog = GetActiveDialogElement(GetNode());
1616 if (dialog) {
1617 AXObject* dialog_object = AXObjectCache().GetOrCreate(dialog);
1618 if (dialog_object) {
1619 ignored_reasons->push_back(
1620 IgnoredReason(kAXActiveModalDialog, dialog_object));
1621 } else {
1622 ignored_reasons->push_back(IgnoredReason(kAXInertElement));
1623 }
1624 } else {
1625 const AXObject* inert_root_el = InertRoot();
1626 if (inert_root_el == this) {
1627 ignored_reasons->push_back(IgnoredReason(kAXInertElement));
1628 } else {
1629 ignored_reasons->push_back(
1630 IgnoredReason(kAXInertSubtree, inert_root_el));
1631 }
1632 }
1633 }
1634 return true;
1635 } else if (IsBlockedByAriaModalDialog(ignored_reasons)) {
1636 return true;
1637 }
1638 } else {
1639 AXObject* parent = ParentObject();
1640 if (parent && parent->IsInertOrAriaHidden()) {
1641 if (ignored_reasons)
1642 parent->ComputeIsInertOrAriaHidden(ignored_reasons);
1643 return true;
1644 }
1645 }
1646
1647 const AXObject* hidden_root = AriaHiddenRoot();
1648 if (hidden_root) {
1649 if (ignored_reasons) {
1650 if (hidden_root == this) {
1651 ignored_reasons->push_back(IgnoredReason(kAXAriaHiddenElement));
1652 } else {
1653 ignored_reasons->push_back(
1654 IgnoredReason(kAXAriaHiddenSubtree, hidden_root));
1655 }
1656 }
1657 return true;
1658 }
1659
1660 return false;
1661 }
1662
IsBlockedByAriaModalDialog(IgnoredReasons * ignored_reasons) const1663 bool AXObject::IsBlockedByAriaModalDialog(
1664 IgnoredReasons* ignored_reasons) const {
1665 AXObject* active_aria_modal_dialog =
1666 AXObjectCache().GetActiveAriaModalDialog();
1667
1668 // On platforms that don't require manual pruning of the accessibility tree,
1669 // the active aria modal dialog should never be set, so has no effect.
1670 if (!active_aria_modal_dialog)
1671 return false;
1672
1673 if (this == active_aria_modal_dialog ||
1674 IsDescendantOf(*active_aria_modal_dialog))
1675 return false;
1676
1677 if (ignored_reasons) {
1678 ignored_reasons->push_back(
1679 IgnoredReason(kAXAriaModalDialog, active_aria_modal_dialog));
1680 }
1681 return true;
1682 }
1683
IsVisible() const1684 bool AXObject::IsVisible() const {
1685 return !IsInertOrAriaHidden() && !IsHiddenViaStyle();
1686 }
1687
IsDescendantOfLeafNode() const1688 bool AXObject::IsDescendantOfLeafNode() const {
1689 UpdateCachedAttributeValuesIfNeeded();
1690 return cached_is_descendant_of_leaf_node_;
1691 }
1692
LeafNodeAncestor() const1693 AXObject* AXObject::LeafNodeAncestor() const {
1694 if (AXObject* parent = ParentObject()) {
1695 if (!parent->CanHaveChildren())
1696 return parent;
1697
1698 return parent->LeafNodeAncestor();
1699 }
1700
1701 return nullptr;
1702 }
1703
AriaHiddenRoot() const1704 const AXObject* AXObject::AriaHiddenRoot() const {
1705 for (const AXObject* object = this; object; object = object->ParentObject()) {
1706 if (object->AOMPropertyOrARIAAttributeIsTrue(AOMBooleanProperty::kHidden))
1707 return object;
1708 }
1709
1710 return nullptr;
1711 }
1712
InertRoot() const1713 const AXObject* AXObject::InertRoot() const {
1714 const AXObject* object = this;
1715 if (!RuntimeEnabledFeatures::InertAttributeEnabled())
1716 return nullptr;
1717
1718 while (object && !object->IsAXNodeObject())
1719 object = object->ParentObject();
1720 Node* node = object->GetNode();
1721 auto* element = DynamicTo<Element>(node);
1722 if (!element)
1723 element = FlatTreeTraversal::ParentElement(*node);
1724
1725 while (element) {
1726 if (element->FastHasAttribute(html_names::kInertAttr))
1727 return AXObjectCache().GetOrCreate(element);
1728 element = FlatTreeTraversal::ParentElement(*element);
1729 }
1730
1731 return nullptr;
1732 }
1733
DispatchEventToAOMEventListeners(Event & event)1734 bool AXObject::DispatchEventToAOMEventListeners(Event& event) {
1735 HeapVector<Member<AccessibleNode>> event_path;
1736 for (AXObject* ancestor = this; ancestor;
1737 ancestor = ancestor->ParentObject()) {
1738 AccessibleNode* ancestor_accessible_node = ancestor->GetAccessibleNode();
1739 if (!ancestor_accessible_node)
1740 continue;
1741
1742 if (!ancestor_accessible_node->HasEventListeners(event.type()))
1743 continue;
1744
1745 event_path.push_back(ancestor_accessible_node);
1746 }
1747
1748 // Short-circuit: if there are no AccessibleNodes attached anywhere
1749 // in the ancestry of this node, exit.
1750 if (!event_path.size())
1751 return false;
1752
1753 // Check if the user has granted permission for this domain to use
1754 // AOM event listeners yet. This may trigger an infobar, but we shouldn't
1755 // block, so whatever decision the user makes will apply to the next
1756 // event received after that.
1757 //
1758 // Note that we only ask the user about this permission the first
1759 // time an event is received that actually would have triggered an
1760 // event listener. However, if the user grants this permission, it
1761 // persists for this origin from then on.
1762 if (!AXObjectCache().CanCallAOMEventListeners()) {
1763 AXObjectCache().RequestAOMEventListenerPermission();
1764 return false;
1765 }
1766
1767 // Since we now know the AOM is being used in this document, get the
1768 // AccessibleNode for the target element and create it if necessary -
1769 // otherwise we wouldn't be able to set the event target. However note
1770 // that if it didn't previously exist it won't be part of the event path.
1771 AccessibleNode* target = GetAccessibleNode();
1772 if (!target) {
1773 if (Element* element = GetElement())
1774 target = element->accessibleNode();
1775 }
1776 if (!target)
1777 return false;
1778 event.SetTarget(target);
1779
1780 // Capturing phase.
1781 event.SetEventPhase(Event::kCapturingPhase);
1782 for (int i = static_cast<int>(event_path.size()) - 1; i >= 0; i--) {
1783 // Don't call capturing event listeners on the target. Note that
1784 // the target may not necessarily be in the event path which is why
1785 // we check here.
1786 if (event_path[i] == target)
1787 break;
1788
1789 event.SetCurrentTarget(event_path[i]);
1790 event_path[i]->FireEventListeners(event);
1791 if (event.PropagationStopped())
1792 return true;
1793 }
1794
1795 // Targeting phase.
1796 event.SetEventPhase(Event::kAtTarget);
1797 event.SetCurrentTarget(event_path[0]);
1798 event_path[0]->FireEventListeners(event);
1799 if (event.PropagationStopped())
1800 return true;
1801
1802 // Bubbling phase.
1803 event.SetEventPhase(Event::kBubblingPhase);
1804 for (wtf_size_t i = 1; i < event_path.size(); i++) {
1805 event.SetCurrentTarget(event_path[i]);
1806 event_path[i]->FireEventListeners(event);
1807 if (event.PropagationStopped())
1808 return true;
1809 }
1810
1811 if (event.defaultPrevented())
1812 return true;
1813
1814 return false;
1815 }
1816
IsDescendantOfDisabledNode() const1817 bool AXObject::IsDescendantOfDisabledNode() const {
1818 UpdateCachedAttributeValuesIfNeeded();
1819 return cached_is_descendant_of_disabled_node_;
1820 }
1821
DisabledAncestor() const1822 const AXObject* AXObject::DisabledAncestor() const {
1823 bool disabled = false;
1824 if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kDisabled, disabled)) {
1825 if (disabled)
1826 return this;
1827 return nullptr;
1828 }
1829
1830 if (AXObject* parent = ParentObject())
1831 return parent->DisabledAncestor();
1832
1833 return nullptr;
1834 }
1835
ComputeAccessibilityIsIgnoredButIncludedInTree() const1836 bool AXObject::ComputeAccessibilityIsIgnoredButIncludedInTree() const {
1837 if (RuntimeEnabledFeatures::AccessibilityExposeIgnoredNodesEnabled())
1838 return true;
1839
1840 if (AXObjectCache().IsAriaOwned(this) || HasARIAOwns(GetElement())) {
1841 // Always include an aria-owned object. It must be a child of the
1842 // element with aria-owns.
1843 return true;
1844 }
1845
1846 if (!GetNode())
1847 return false;
1848
1849 // Use a flag to control whether or not the <html> element is included
1850 // in the accessibility tree. Either way it's always marked as "ignored",
1851 // but eventually we want to always include it in the tree to simplify
1852 // some logic.
1853 if (GetNode() && IsA<HTMLHtmlElement>(GetNode()))
1854 return RuntimeEnabledFeatures::AccessibilityExposeHTMLElementEnabled();
1855
1856 // If the node is part of the user agent shadow dom, or has the explicit
1857 // internal Role::kIgnored, they aren't interesting for paragraph navigation
1858 // or LabelledBy/DescribedBy relationships.
1859 if (RoleValue() == ax::mojom::blink::Role::kIgnored ||
1860 GetNode()->IsInUserAgentShadowRoot()) {
1861 return false;
1862 }
1863
1864 // Keep the internal accessibility tree consistent for videos which lack
1865 // a player and also inner text.
1866 if (RoleValue() == ax::mojom::blink::Role::kVideo ||
1867 RoleValue() == ax::mojom::blink::Role::kAudio) {
1868 return true;
1869 }
1870
1871 // Always pass through Line Breaking objects, this is necessary to
1872 // detect paragraph edges, which are defined as hard-line breaks.
1873 if (IsLineBreakingObject())
1874 return true;
1875
1876 // Allow the browser side ax tree to access "visibility: [hidden|collapse]"
1877 // and "display: none" nodes. This is useful for APIs that return the node
1878 // referenced by aria-labeledby and aria-describedby.
1879 // An element must have an id attribute or it cannot be referenced by
1880 // aria-labelledby or aria-describedby.
1881 if (RuntimeEnabledFeatures::AccessibilityExposeDisplayNoneEnabled()) {
1882 if (Element* element = GetElement()) {
1883 if (element->FastHasAttribute(html_names::kIdAttr) &&
1884 IsHiddenViaStyle()) {
1885 return true;
1886 }
1887 }
1888 } else if (GetLayoutObject()) {
1889 if (GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible)
1890 return true;
1891 }
1892
1893 // Allow the browser side ax tree to access "aria-hidden" nodes.
1894 // This is useful for APIs that return the node referenced by
1895 // aria-labeledby and aria-describedby.
1896 if (GetLayoutObject() && AriaHiddenRoot())
1897 return true;
1898
1899 // Preserve SVG grouping elements.
1900 if (GetNode() && IsA<SVGGElement>(GetNode()))
1901 return true;
1902
1903 // Preserve nodes with language attributes.
1904 if (HasAttribute(html_names::kLangAttr))
1905 return true;
1906
1907 return false;
1908 }
1909
GetNativeTextControlAncestor(int max_levels_to_check) const1910 const AXObject* AXObject::GetNativeTextControlAncestor(
1911 int max_levels_to_check) const {
1912 if (IsNativeTextControl())
1913 return this;
1914
1915 if (max_levels_to_check == 0)
1916 return nullptr;
1917
1918 if (AXObject* parent = ParentObject())
1919 return parent->GetNativeTextControlAncestor(max_levels_to_check - 1);
1920
1921 return nullptr;
1922 }
1923
DatetimeAncestor(int max_levels_to_check) const1924 const AXObject* AXObject::DatetimeAncestor(int max_levels_to_check) const {
1925 switch (RoleValue()) {
1926 case ax::mojom::blink::Role::kDateTime:
1927 case ax::mojom::blink::Role::kDate:
1928 case ax::mojom::blink::Role::kInputTime:
1929 case ax::mojom::blink::Role::kTime:
1930 return this;
1931 default:
1932 break;
1933 }
1934
1935 if (max_levels_to_check == 0)
1936 return nullptr;
1937
1938 if (AXObject* parent = ParentObject())
1939 return parent->DatetimeAncestor(max_levels_to_check - 1);
1940
1941 return nullptr;
1942 }
1943
LastKnownIsIgnoredValue() const1944 bool AXObject::LastKnownIsIgnoredValue() const {
1945 if (last_known_is_ignored_value_ == kDefaultBehavior) {
1946 last_known_is_ignored_value_ =
1947 AccessibilityIsIgnored() ? kIgnoreObject : kIncludeObject;
1948 }
1949
1950 return last_known_is_ignored_value_ == kIgnoreObject;
1951 }
1952
SetLastKnownIsIgnoredValue(bool is_ignored)1953 void AXObject::SetLastKnownIsIgnoredValue(bool is_ignored) {
1954 last_known_is_ignored_value_ = is_ignored ? kIgnoreObject : kIncludeObject;
1955 }
1956
LastKnownIsIgnoredButIncludedInTreeValue() const1957 bool AXObject::LastKnownIsIgnoredButIncludedInTreeValue() const {
1958 if (last_known_is_ignored_but_included_in_tree_value_ == kDefaultBehavior) {
1959 last_known_is_ignored_but_included_in_tree_value_ =
1960 AccessibilityIsIgnoredButIncludedInTree() ? kIncludeObject
1961 : kIgnoreObject;
1962 }
1963
1964 return last_known_is_ignored_but_included_in_tree_value_ == kIncludeObject;
1965 }
1966
SetLastKnownIsIgnoredButIncludedInTreeValue(bool is_ignored_but_included_in_tree)1967 void AXObject::SetLastKnownIsIgnoredButIncludedInTreeValue(
1968 bool is_ignored_but_included_in_tree) {
1969 last_known_is_ignored_but_included_in_tree_value_ =
1970 is_ignored_but_included_in_tree ? kIncludeObject : kIgnoreObject;
1971 }
1972
LastKnownIsIncludedInTreeValue() const1973 bool AXObject::LastKnownIsIncludedInTreeValue() const {
1974 return !LastKnownIsIgnoredValue() ||
1975 LastKnownIsIgnoredButIncludedInTreeValue();
1976 }
1977
HasInheritedPresentationalRole() const1978 bool AXObject::HasInheritedPresentationalRole() const {
1979 UpdateCachedAttributeValuesIfNeeded();
1980 return cached_has_inherited_presentational_role_;
1981 }
1982
CanSetValueAttribute() const1983 bool AXObject::CanSetValueAttribute() const {
1984 switch (RoleValue()) {
1985 case ax::mojom::blink::Role::kColorWell:
1986 case ax::mojom::blink::Role::kDate:
1987 case ax::mojom::blink::Role::kDateTime:
1988 case ax::mojom::blink::Role::kInputTime:
1989 case ax::mojom::blink::Role::kScrollBar:
1990 case ax::mojom::blink::Role::kSearchBox:
1991 case ax::mojom::blink::Role::kSlider:
1992 case ax::mojom::blink::Role::kSpinButton:
1993 case ax::mojom::blink::Role::kSplitter:
1994 case ax::mojom::blink::Role::kTextField:
1995 case ax::mojom::blink::Role::kTextFieldWithComboBox:
1996 return Restriction() == kRestrictionNone;
1997 default:
1998 return false;
1999 }
2000 }
2001
IsFocusableStyleUsingBestAvailableState() const2002 bool AXObject::IsFocusableStyleUsingBestAvailableState() const {
2003 auto* element = GetElement();
2004 DCHECK(element);
2005
2006 // If this element's layout tree does not need an update, it means that we can
2007 // rely on Element's IsFocusableStyle directly, which is the best available
2008 // source of information.
2009 // Note that we also allow this to be used if we're in a style recalc, since
2010 // we might get here through layout object attachment. In that case, the dirty
2011 // bits may not have been cleared yet, but all relevant style and layout tree
2012 // should be up to date. Note that this quirk can be fixed by deferring AX
2013 // tree updates to happen after the layout tree attachment has finished.
2014 if (GetDocument()->InStyleRecalc() ||
2015 !GetDocument()->NeedsLayoutTreeUpdateForNodeIncludingDisplayLocked(
2016 *element)) {
2017 return element->IsFocusableStyle();
2018 }
2019
2020 // The best available source of information is now the AX tree, so use that to
2021 // figure out whether we have focusable style.
2022
2023 // If we're in a canvas subtree, then use the canvas visibility instead of
2024 // self visibility. The elements in a canvas subtree are fallback elements,
2025 // which are not necessarily rendered but are allowed to be focusable.
2026 if (element->IsInCanvasSubtree()) {
2027 const HTMLCanvasElement* canvas =
2028 Traversal<HTMLCanvasElement>::FirstAncestorOrSelf(*element);
2029 DCHECK(canvas);
2030 return canvas->GetLayoutObject() &&
2031 canvas->GetLayoutObject()->Style()->Visibility() ==
2032 EVisibility::kVisible;
2033 }
2034
2035 return GetLayoutObject() &&
2036 GetLayoutObject()->Style()->Visibility() == EVisibility::kVisible;
2037 }
2038
2039 // This does not use Element::IsFocusable(), as that can sometimes recalculate
2040 // styles because of IsFocusableStyle() check, resetting the document lifecycle.
CanSetFocusAttribute() const2041 bool AXObject::CanSetFocusAttribute() const {
2042 if (IsDetached())
2043 return false;
2044
2045 // Objects within a portal are not focusable.
2046 // Note that they are ignored but can be included in the tree.
2047 bool inside_portal = GetDocument() && GetDocument()->GetPage() &&
2048 GetDocument()->GetPage()->InsidePortal();
2049 if (inside_portal)
2050 return false;
2051
2052 // Display-locked nodes that have content-visibility: hidden are not exposed
2053 // to accessibility in any way, so they are not focusable. Note that for
2054 // content-visibility: auto cases, `ShouldIgnoreNodeDueToDisplayLock()` would
2055 // return false, since we're not ignoring the element in that case.
2056 if (GetNode() &&
2057 DisplayLockUtilities::ShouldIgnoreNodeDueToDisplayLock(
2058 *GetNode(), DisplayLockActivationReason::kAccessibility)) {
2059 return false;
2060 }
2061
2062 // Focusable: web area -- this is the only focusable non-element. Web areas
2063 // inside portals are not focusable though (portal contents cannot get focus).
2064 if (IsWebArea())
2065 return true;
2066
2067 // NOT focusable: objects with no DOM node, e.g. extra layout blocks inserted
2068 // as filler, or objects where the node is not an element, such as a text
2069 // node or an HTML comment.
2070 Element* elem = GetElement();
2071 if (!elem)
2072 return false;
2073
2074 // NOT focusable: inert elements.
2075 if (elem->IsInert())
2076 return false;
2077
2078 // NOT focusable: disabled form controls.
2079 if (IsDisabledFormControl(elem))
2080 return false;
2081
2082 // Focusable: options in a combobox or listbox.
2083 // Even though they are not treated as supporting focus by Blink (the parent
2084 // widget is), they are considered focusable in the accessibility sense,
2085 // behaving like potential active descendants, and handling focus actions.
2086 // Menu list options are handled before visibility check, because they
2087 // are considered focusable even when part of collapsed drop down.
2088 if (RoleValue() == ax::mojom::blink::Role::kMenuListOption)
2089 return true;
2090
2091 // NOT focusable: hidden elements.
2092 // TODO(aleventhal) Consider caching visibility when it's safe to compute.
2093 if (!IsA<HTMLAreaElement>(elem) && !IsFocusableStyleUsingBestAvailableState())
2094 return false;
2095
2096 // Focusable: options in a combobox or listbox.
2097 // Similar to menu list option treatment above, but not focusable if hidden.
2098 if (RoleValue() == ax::mojom::blink::Role::kListBoxOption)
2099 return true;
2100
2101 // Focusable: element supports focus.
2102 if (elem->SupportsFocus())
2103 return true;
2104
2105 // TODO(accessibility) Focusable: scrollable with the keyboard.
2106 // Keyboard-focusable scroll containers feature:
2107 // https://www.chromestatus.com/feature/5231964663578624
2108 // When adding here, remove similar check from ::SupportsNameFromContents().
2109 // if (RuntimeEnabledFeatures::KeyboardFocusableScrollersEnabled() &&
2110 // IsUserScrollable()) {
2111 // return true;
2112 // }
2113
2114 // Focusable: can be an active descendant.
2115 if (CanBeActiveDescendant())
2116 return true;
2117
2118 // NOT focusable: everything else.
2119 return false;
2120 }
2121
2122 // From ARIA 1.1.
2123 // 1. The value of aria-activedescendant refers to an element that is either a
2124 // descendant of the element with DOM focus or is a logical descendant as
2125 // indicated by the aria-owns attribute. 2. The element with DOM focus is a
2126 // textbox with aria-controls referring to an element that supports
2127 // aria-activedescendant, and the value of aria-activedescendant specified for
2128 // the textbox refers to either a descendant of the element controlled by the
2129 // textbox or is a logical descendant of that controlled element as indicated by
2130 // the aria-owns attribute.
CanBeActiveDescendant() const2131 bool AXObject::CanBeActiveDescendant() const {
2132 // Require an element with an id attribute.
2133 // TODO(accessibility): this code currently requires both an id and role
2134 // attribute, as well as an ancestor or controlling aria-activedescendant.
2135 // However, with element reflection it may be possible to set an active
2136 // descendant without an id, so at some point we may need to remove the
2137 // requirement for an id attribute.
2138 if (!GetElement() || !GetElement()->FastHasAttribute(html_names::kIdAttr))
2139 return false;
2140
2141 // Does not make sense to use aria-activedescendant to point to a
2142 // presentational object.
2143 if (IsPresentational())
2144 return false;
2145
2146 // Does not make sense to use aria-activedescendant to point to an HTML
2147 // element that requires real focus, therefore an ARIA role is necessary.
2148 if (AriaRoleAttribute() == ax::mojom::blink::Role::kUnknown)
2149 return false;
2150
2151 return IsARIAControlledByTextboxWithActiveDescendant() ||
2152 AncestorExposesActiveDescendant();
2153 }
2154
UpdateDistributionForFlatTreeTraversal() const2155 void AXObject::UpdateDistributionForFlatTreeTraversal() const {
2156 Node* node = GetNode();
2157 if (!node) {
2158 AXObject* parent = this->ParentObject();
2159 while (!node && parent) {
2160 node = parent->GetNode();
2161 parent = parent->ParentObject();
2162 }
2163 }
2164
2165 if (node)
2166 node->UpdateDistributionForFlatTreeTraversal();
2167
2168 // TODO(aboxhall): Instead of this, propagate inert down through frames
2169 Document* document = GetDocument();
2170 while (document && document->LocalOwner()) {
2171 document->LocalOwner()->UpdateDistributionForFlatTreeTraversal();
2172 document = document->LocalOwner()->ownerDocument();
2173 }
2174 }
2175
IsARIAControlledByTextboxWithActiveDescendant() const2176 bool AXObject::IsARIAControlledByTextboxWithActiveDescendant() const {
2177 if (IsDetached() || !GetDocument())
2178 return false;
2179
2180 // This situation should mostly arise when using an active descendant on a
2181 // textbox inside an ARIA 1.1 combo box widget, which points to the selected
2182 // option in a list. In such situations, the active descendant is useful only
2183 // when the textbox is focused. Therefore, we don't currently need to keep
2184 // track of all aria-controls relationships.
2185 const Element* focused_element = GetDocument()->FocusedElement();
2186 if (!focused_element)
2187 return false;
2188
2189 const AXObject* focused_object = AXObjectCache().GetOrCreate(focused_element);
2190 if (!focused_object || !focused_object->IsTextControl())
2191 return false;
2192
2193 if (!focused_object->GetAOMPropertyOrARIAAttribute(
2194 AOMRelationProperty::kActiveDescendant)) {
2195 return false;
2196 }
2197
2198 HeapVector<Member<Element>> controlled_by_elements;
2199 if (!focused_object->HasAOMPropertyOrARIAAttribute(
2200 AOMRelationListProperty::kControls, controlled_by_elements)) {
2201 return false;
2202 }
2203
2204 for (const auto& controlled_by_element : controlled_by_elements) {
2205 const AXObject* controlled_by_object =
2206 AXObjectCache().GetOrCreate(controlled_by_element);
2207 if (!controlled_by_object)
2208 continue;
2209
2210 const AXObject* object = this;
2211 while (object && object != controlled_by_object)
2212 object = object->ParentObjectUnignored();
2213 if (object)
2214 return true;
2215 }
2216
2217 return false;
2218 }
2219
AncestorExposesActiveDescendant() const2220 bool AXObject::AncestorExposesActiveDescendant() const {
2221 const AXObject* parent = ParentObjectUnignored();
2222 if (!parent)
2223 return false;
2224
2225 if (parent->GetAOMPropertyOrARIAAttribute(
2226 AOMRelationProperty::kActiveDescendant)) {
2227 return true;
2228 }
2229
2230 return parent->AncestorExposesActiveDescendant();
2231 }
2232
HasIndirectChildren() const2233 bool AXObject::HasIndirectChildren() const {
2234 return RoleValue() == ax::mojom::blink::Role::kTableHeaderContainer;
2235 }
2236
CanSetSelectedAttribute() const2237 bool AXObject::CanSetSelectedAttribute() const {
2238 // Sub-widget elements can be selected if not disabled (native or ARIA)
2239 return IsSubWidget() && Restriction() != kRestrictionDisabled;
2240 }
2241
IsSubWidget() const2242 bool AXObject::IsSubWidget() const {
2243 switch (RoleValue()) {
2244 case ax::mojom::blink::Role::kCell:
2245 case ax::mojom::blink::Role::kColumnHeader:
2246 case ax::mojom::blink::Role::kRowHeader:
2247 case ax::mojom::blink::Role::kColumn:
2248 case ax::mojom::blink::Role::kRow:
2249 // If it has an explicit ARIA role, it's a subwidget.
2250 //
2251 // Reasoning:
2252 // Static table cells are not selectable, but ARIA grid cells
2253 // and rows definitely are according to the spec. To support
2254 // ARIA 1.0, it's sufficient to just check if there's any
2255 // ARIA role at all, because if so then it must be a grid-related
2256 // role so it must be selectable.
2257 //
2258 // TODO(accessibility): an ARIA 1.1+ role of "cell", or a role of "row"
2259 // inside an ARIA 1.1 role of "table", should not be selectable. We may
2260 // need to create separate role enums for grid cells vs table cells
2261 // to implement this.
2262 if (AriaRoleAttribute() != ax::mojom::blink::Role::kUnknown)
2263 return true;
2264
2265 // Otherwise it's only a subwidget if it's in a grid or treegrid,
2266 // not in a table.
2267 return std::any_of(
2268 UnignoredAncestorsBegin(), UnignoredAncestorsEnd(),
2269 [](const AXObject& ancestor) {
2270 return ancestor.RoleValue() == ax::mojom::blink::Role::kGrid ||
2271 ancestor.RoleValue() == ax::mojom::blink::Role::kTreeGrid;
2272 });
2273
2274 case ax::mojom::blink::Role::kListBoxOption:
2275 case ax::mojom::blink::Role::kMenuListOption:
2276 case ax::mojom::blink::Role::kTab:
2277 case ax::mojom::blink::Role::kTreeItem:
2278 return true;
2279 default:
2280 return false;
2281 }
2282 }
2283
SupportsARIASetSizeAndPosInSet() const2284 bool AXObject::SupportsARIASetSizeAndPosInSet() const {
2285 return ui::IsSetLike(RoleValue()) || ui::IsItemLike(RoleValue());
2286 }
2287
2288 // Simplify whitespace, but preserve a single leading and trailing whitespace
2289 // character if it's present.
2290 // static
CollapseWhitespace(const String & str)2291 String AXObject::CollapseWhitespace(const String& str) {
2292 StringBuilder result;
2293 if (!str.IsEmpty() && IsHTMLSpace<UChar>(str[0]))
2294 result.Append(' ');
2295 result.Append(str.SimplifyWhiteSpace(IsHTMLSpace<UChar>));
2296 if (!str.IsEmpty() && IsHTMLSpace<UChar>(str[str.length() - 1]))
2297 result.Append(' ');
2298 return result.ToString();
2299 }
2300
ComputedName() const2301 String AXObject::ComputedName() const {
2302 ax::mojom::blink::NameFrom name_from;
2303 AXObjectVector name_objects;
2304 return GetName(name_from, &name_objects);
2305 }
2306
GetName(ax::mojom::blink::NameFrom & name_from,AXObject::AXObjectVector * name_objects) const2307 String AXObject::GetName(ax::mojom::blink::NameFrom& name_from,
2308 AXObject::AXObjectVector* name_objects) const {
2309 HeapHashSet<Member<const AXObject>> visited;
2310 AXRelatedObjectVector related_objects;
2311
2312 // Initialize |name_from|, as TextAlternative() might never set it in some
2313 // cases.
2314 name_from = ax::mojom::blink::NameFrom::kNone;
2315 String text = TextAlternative(false, false, visited, name_from,
2316 &related_objects, nullptr);
2317
2318 ax::mojom::blink::Role role = RoleValue();
2319 if (!GetNode() || (!IsA<HTMLBRElement>(GetNode()) &&
2320 role != ax::mojom::blink::Role::kStaticText &&
2321 role != ax::mojom::blink::Role::kInlineTextBox))
2322 text = CollapseWhitespace(text);
2323
2324 if (name_objects) {
2325 name_objects->clear();
2326 for (NameSourceRelatedObject* related_object : related_objects)
2327 name_objects->push_back(related_object->object);
2328 }
2329
2330 return text;
2331 }
2332
GetName(NameSources * name_sources) const2333 String AXObject::GetName(NameSources* name_sources) const {
2334 AXObjectSet visited;
2335 ax::mojom::blink::NameFrom tmp_name_from;
2336 AXRelatedObjectVector tmp_related_objects;
2337 String text = TextAlternative(false, false, visited, tmp_name_from,
2338 &tmp_related_objects, name_sources);
2339 text = text.SimplifyWhiteSpace(IsHTMLSpace<UChar>);
2340 return text;
2341 }
2342
RecursiveTextAlternative(const AXObject & ax_obj,bool in_aria_labelled_by_traversal,AXObjectSet & visited)2343 String AXObject::RecursiveTextAlternative(const AXObject& ax_obj,
2344 bool in_aria_labelled_by_traversal,
2345 AXObjectSet& visited) {
2346 ax::mojom::blink::NameFrom tmp_name_from;
2347 return RecursiveTextAlternative(ax_obj, in_aria_labelled_by_traversal,
2348 visited, tmp_name_from);
2349 }
2350
RecursiveTextAlternative(const AXObject & ax_obj,bool in_aria_labelled_by_traversal,AXObjectSet & visited,ax::mojom::blink::NameFrom & name_from)2351 String AXObject::RecursiveTextAlternative(
2352 const AXObject& ax_obj,
2353 bool in_aria_labelled_by_traversal,
2354 AXObjectSet& visited,
2355 ax::mojom::blink::NameFrom& name_from) {
2356 if (visited.Contains(&ax_obj) && !in_aria_labelled_by_traversal)
2357 return String();
2358
2359 return ax_obj.TextAlternative(true, in_aria_labelled_by_traversal, visited,
2360 name_from, nullptr, nullptr);
2361 }
2362
ComputeIsHiddenViaStyle() const2363 bool AXObject::ComputeIsHiddenViaStyle() const {
2364 Node* node = GetNode();
2365 if (!node)
2366 return false;
2367
2368 // Display-locked nodes are always hidden.
2369 if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*node))
2370 return true;
2371
2372 // Style elements in SVG are not display: none, unlike HTML style elements,
2373 // but they are still hidden and thus treated as hidden from style.
2374 if (IsA<SVGStyleElement>(node))
2375 return true;
2376
2377 // For elements with layout objects we can get their style directly.
2378 if (GetLayoutObject())
2379 return GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible;
2380
2381 // No layout object: must ensure computed style.
2382 if (Element* element = DynamicTo<Element>(node)) {
2383 const ComputedStyle* style = element->EnsureComputedStyle();
2384 return !style || style->IsEnsuredInDisplayNone() ||
2385 style->Visibility() != EVisibility::kVisible;
2386 }
2387 return false;
2388 }
2389
IsHiddenViaStyle() const2390 bool AXObject::IsHiddenViaStyle() const {
2391 UpdateCachedAttributeValuesIfNeeded();
2392 return cached_is_hidden_via_style;
2393 }
2394
2395 // Return true if this should be removed from accessible name computations,
2396 // unless it is reached by following an aria-labelledby. When that happens, this
2397 // is not checked, because aria-labelledby can use hidden subtrees.
2398 // Because aria-labelledby can use hidden subtrees, when it has entered a hidden
2399 // subtree, it is not enough to check if the element was hidden by an ancestor.
2400 // In this case, return true only if the hiding style targeted the node
2401 // directly, as opposed to having inherited the hiding style. Using inherited
2402 // hiding styles is problematic because it would prevent name contributions from
2403 // deeper nodes in hidden aria-labelledby subtrees.
IsHiddenForTextAlternativeCalculation() const2404 bool AXObject::IsHiddenForTextAlternativeCalculation() const {
2405 // aria-hidden=false allows hidden contents to be used in name from contents.
2406 if (AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty::kHidden))
2407 return false;
2408
2409 auto* node = GetNode();
2410 if (!node)
2411 return false;
2412
2413 // Display-locked elements are available for text/name resolution.
2414 if (DisplayLockUtilities::NearestLockedExclusiveAncestor(*node))
2415 return false;
2416
2417 Document* document = GetDocument();
2418 if (!document || !document->GetFrame())
2419 return false;
2420
2421 // Do not contribute <noscript> to text alternative of an ancestor.
2422 if (IsA<HTMLNoScriptElement>(node))
2423 return true;
2424
2425 // Always contribute SVG <title> despite it having a hidden style by default.
2426 if (IsA<SVGTitleElement>(node))
2427 return false;
2428
2429 // If this is hidden but its parent isn't, then it appears the hiding style
2430 // targeted this node directly. Do not recurse into it for name from contents.
2431 return IsHiddenViaStyle() &&
2432 (!ParentObject() || !ParentObject()->IsHiddenViaStyle());
2433 }
2434
AriaTextAlternative(bool recursive,bool in_aria_labelled_by_traversal,AXObjectSet & visited,ax::mojom::blink::NameFrom & name_from,AXRelatedObjectVector * related_objects,NameSources * name_sources,bool * found_text_alternative) const2435 String AXObject::AriaTextAlternative(bool recursive,
2436 bool in_aria_labelled_by_traversal,
2437 AXObjectSet& visited,
2438 ax::mojom::blink::NameFrom& name_from,
2439 AXRelatedObjectVector* related_objects,
2440 NameSources* name_sources,
2441 bool* found_text_alternative) const {
2442 String text_alternative;
2443 bool already_visited = visited.Contains(this);
2444 visited.insert(this);
2445
2446 // Step 2A from: http://www.w3.org/TR/accname-aam-1.1
2447 // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
2448 if (!in_aria_labelled_by_traversal &&
2449 IsHiddenForTextAlternativeCalculation()) {
2450 *found_text_alternative = true;
2451 return String();
2452 }
2453
2454 // Step 2B from: http://www.w3.org/TR/accname-aam-1.1
2455 // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
2456 if (!in_aria_labelled_by_traversal && !already_visited) {
2457 name_from = ax::mojom::blink::NameFrom::kRelatedElement;
2458
2459 // Check ARIA attributes.
2460 const QualifiedName& attr =
2461 HasAttribute(html_names::kAriaLabeledbyAttr) &&
2462 !HasAttribute(html_names::kAriaLabelledbyAttr)
2463 ? html_names::kAriaLabeledbyAttr
2464 : html_names::kAriaLabelledbyAttr;
2465
2466 if (name_sources) {
2467 name_sources->push_back(NameSource(*found_text_alternative, attr));
2468 name_sources->back().type = name_from;
2469 }
2470
2471 Element* element = GetElement();
2472 if (element) {
2473 HeapVector<Member<Element>> elements_from_attribute;
2474 Vector<String> ids;
2475 ElementsFromAttribute(elements_from_attribute, attr, ids);
2476
2477 const AtomicString& aria_labelledby = GetAttribute(attr);
2478
2479 if (!aria_labelledby.IsNull()) {
2480 if (name_sources)
2481 name_sources->back().attribute_value = aria_labelledby;
2482
2483 // Operate on a copy of |visited| so that if |name_sources| is not
2484 // null, the set of visited objects is preserved unmodified for future
2485 // calculations.
2486 AXObjectSet visited_copy = visited;
2487 text_alternative = TextFromElements(
2488 true, visited, elements_from_attribute, related_objects);
2489 if (!ids.IsEmpty())
2490 AXObjectCache().UpdateReverseRelations(this, ids);
2491 if (!text_alternative.IsNull()) {
2492 if (name_sources) {
2493 NameSource& source = name_sources->back();
2494 source.type = name_from;
2495 source.related_objects = *related_objects;
2496 source.text = text_alternative;
2497 *found_text_alternative = true;
2498 } else {
2499 *found_text_alternative = true;
2500 return text_alternative;
2501 }
2502 } else if (name_sources) {
2503 name_sources->back().invalid = true;
2504 }
2505 }
2506 }
2507 }
2508
2509 // Step 2C from: http://www.w3.org/TR/accname-aam-1.1
2510 // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
2511 name_from = ax::mojom::blink::NameFrom::kAttribute;
2512 if (name_sources) {
2513 name_sources->push_back(
2514 NameSource(*found_text_alternative, html_names::kAriaLabelAttr));
2515 name_sources->back().type = name_from;
2516 }
2517 const AtomicString& aria_label =
2518 GetAOMPropertyOrARIAAttribute(AOMStringProperty::kLabel);
2519 if (!aria_label.IsEmpty()) {
2520 text_alternative = aria_label;
2521
2522 if (name_sources) {
2523 NameSource& source = name_sources->back();
2524 source.text = text_alternative;
2525 source.attribute_value = aria_label;
2526 *found_text_alternative = true;
2527 } else {
2528 *found_text_alternative = true;
2529 return text_alternative;
2530 }
2531 }
2532
2533 return text_alternative;
2534 }
2535
TextFromElements(bool in_aria_labelledby_traversal,AXObjectSet & visited,HeapVector<Member<Element>> & elements,AXRelatedObjectVector * related_objects) const2536 String AXObject::TextFromElements(
2537 bool in_aria_labelledby_traversal,
2538 AXObjectSet& visited,
2539 HeapVector<Member<Element>>& elements,
2540 AXRelatedObjectVector* related_objects) const {
2541 StringBuilder accumulated_text;
2542 bool found_valid_element = false;
2543 AXRelatedObjectVector local_related_objects;
2544
2545 for (const auto& element : elements) {
2546 AXObject* ax_element = AXObjectCache().GetOrCreate(element);
2547 if (ax_element) {
2548 found_valid_element = true;
2549
2550 String result = RecursiveTextAlternative(
2551 *ax_element, in_aria_labelledby_traversal, visited);
2552 visited.insert(ax_element);
2553 local_related_objects.push_back(
2554 MakeGarbageCollected<NameSourceRelatedObject>(ax_element, result));
2555 if (!result.IsEmpty()) {
2556 if (!accumulated_text.IsEmpty())
2557 accumulated_text.Append(' ');
2558 accumulated_text.Append(result);
2559 }
2560 }
2561 }
2562 if (!found_valid_element)
2563 return String();
2564 if (related_objects)
2565 *related_objects = local_related_objects;
2566 return accumulated_text.ToString();
2567 }
2568
TokenVectorFromAttribute(Vector<String> & tokens,const QualifiedName & attribute) const2569 void AXObject::TokenVectorFromAttribute(Vector<String>& tokens,
2570 const QualifiedName& attribute) const {
2571 Node* node = this->GetNode();
2572 if (!node || !node->IsElementNode())
2573 return;
2574
2575 String attribute_value = GetAttribute(attribute).GetString();
2576 if (attribute_value.IsEmpty())
2577 return;
2578
2579 attribute_value = attribute_value.SimplifyWhiteSpace();
2580 attribute_value.Split(' ', tokens);
2581 }
2582
ElementsFromAttribute(HeapVector<Member<Element>> & elements,const QualifiedName & attribute,Vector<String> & ids) const2583 void AXObject::ElementsFromAttribute(HeapVector<Member<Element>>& elements,
2584 const QualifiedName& attribute,
2585 Vector<String>& ids) const {
2586 // We compute the attr-associated elements, which are either explicitly set
2587 // element references set via the IDL, or computed from the content attribute.
2588 TokenVectorFromAttribute(ids, attribute);
2589 Element* element = GetElement();
2590 if (!element)
2591 return;
2592
2593 base::Optional<HeapVector<Member<Element>>> attr_associated_elements =
2594 element->GetElementArrayAttribute(attribute);
2595 if (!attr_associated_elements)
2596 return;
2597
2598 for (const auto& element : attr_associated_elements.value())
2599 elements.push_back(element);
2600 }
2601
AriaLabelledbyElementVector(HeapVector<Member<Element>> & elements,Vector<String> & ids) const2602 void AXObject::AriaLabelledbyElementVector(
2603 HeapVector<Member<Element>>& elements,
2604 Vector<String>& ids) const {
2605 // Try both spellings, but prefer aria-labelledby, which is the official spec.
2606 ElementsFromAttribute(elements, html_names::kAriaLabelledbyAttr, ids);
2607 if (!ids.size())
2608 ElementsFromAttribute(elements, html_names::kAriaLabeledbyAttr, ids);
2609 }
2610
TextFromAriaLabelledby(AXObjectSet & visited,AXRelatedObjectVector * related_objects,Vector<String> & ids) const2611 String AXObject::TextFromAriaLabelledby(AXObjectSet& visited,
2612 AXRelatedObjectVector* related_objects,
2613 Vector<String>& ids) const {
2614 HeapVector<Member<Element>> elements;
2615 AriaLabelledbyElementVector(elements, ids);
2616 return TextFromElements(true, visited, elements, related_objects);
2617 }
2618
TextFromAriaDescribedby(AXRelatedObjectVector * related_objects,Vector<String> & ids) const2619 String AXObject::TextFromAriaDescribedby(AXRelatedObjectVector* related_objects,
2620 Vector<String>& ids) const {
2621 AXObjectSet visited;
2622 HeapVector<Member<Element>> elements;
2623 ElementsFromAttribute(elements, html_names::kAriaDescribedbyAttr, ids);
2624 return TextFromElements(true, visited, elements, related_objects);
2625 }
2626
BackgroundColor() const2627 RGBA32 AXObject::BackgroundColor() const {
2628 UpdateCachedAttributeValuesIfNeeded();
2629 return cached_background_color_;
2630 }
2631
Orientation() const2632 AccessibilityOrientation AXObject::Orientation() const {
2633 // In ARIA 1.1, the default value for aria-orientation changed from
2634 // horizontal to undefined.
2635 return kAccessibilityOrientationUndefined;
2636 }
2637
LoadInlineTextBoxes()2638 void AXObject::LoadInlineTextBoxes() {}
2639
NextOnLine() const2640 AXObject* AXObject::NextOnLine() const {
2641 return nullptr;
2642 }
2643
PreviousOnLine() const2644 AXObject* AXObject::PreviousOnLine() const {
2645 return nullptr;
2646 }
2647
2648 base::Optional<const DocumentMarker::MarkerType>
GetAriaSpellingOrGrammarMarker() const2649 AXObject::GetAriaSpellingOrGrammarMarker() const {
2650 AtomicString aria_invalid_value;
2651 const AncestorsIterator iter = std::find_if(
2652 UnignoredAncestorsBegin(), UnignoredAncestorsEnd(),
2653 [&aria_invalid_value](const AXObject& ancestor) {
2654 return ancestor.HasAOMPropertyOrARIAAttribute(
2655 AOMStringProperty::kInvalid, aria_invalid_value) ||
2656 ancestor.IsLineBreakingObject();
2657 });
2658
2659 if (iter == UnignoredAncestorsEnd())
2660 return base::nullopt;
2661 if (EqualIgnoringASCIICase(aria_invalid_value, "spelling"))
2662 return DocumentMarker::kSpelling;
2663 if (EqualIgnoringASCIICase(aria_invalid_value, "grammar"))
2664 return DocumentMarker::kGrammar;
2665 return base::nullopt;
2666 }
2667
GetDocumentMarkers(VectorOf<DocumentMarker::MarkerType> * marker_types,VectorOf<AXRange> * marker_ranges) const2668 void AXObject::GetDocumentMarkers(
2669 VectorOf<DocumentMarker::MarkerType>* marker_types,
2670 VectorOf<AXRange>* marker_ranges) const {}
2671
TextCharacterOffsets(Vector<int> &) const2672 void AXObject::TextCharacterOffsets(Vector<int>&) const {}
2673
GetWordBoundaries(Vector<int> & word_starts,Vector<int> & word_ends) const2674 void AXObject::GetWordBoundaries(Vector<int>& word_starts,
2675 Vector<int>& word_ends) const {}
2676
TextOffsetInFormattingContext(int offset) const2677 int AXObject::TextOffsetInFormattingContext(int offset) const {
2678 DCHECK_GE(offset, 0);
2679 return offset;
2680 }
2681
TextOffsetInContainer(int offset) const2682 int AXObject::TextOffsetInContainer(int offset) const {
2683 DCHECK_GE(offset, 0);
2684 return offset;
2685 }
2686
Action() const2687 ax::mojom::blink::DefaultActionVerb AXObject::Action() const {
2688 Element* action_element = ActionElement();
2689 if (!action_element)
2690 return ax::mojom::blink::DefaultActionVerb::kNone;
2691
2692 // TODO(dmazzoni): Ensure that combo box text field is handled here.
2693 if (IsTextControl())
2694 return ax::mojom::blink::DefaultActionVerb::kActivate;
2695
2696 if (IsCheckable()) {
2697 return CheckedState() != ax::mojom::blink::CheckedState::kTrue
2698 ? ax::mojom::blink::DefaultActionVerb::kCheck
2699 : ax::mojom::blink::DefaultActionVerb::kUncheck;
2700 }
2701
2702 // If this object cannot receive focus and has a button role, use click as
2703 // the default action. On the AuraLinux platform, the press action is a
2704 // signal to users that they can trigger the action using the keyboard, while
2705 // a click action means the user should trigger the action via a simulated
2706 // click. If this object cannot receive focus, it's impossible to trigger it
2707 // with a key press.
2708 if (RoleValue() == ax::mojom::blink::Role::kButton && !CanSetFocusAttribute())
2709 return ax::mojom::blink::DefaultActionVerb::kClick;
2710
2711 switch (RoleValue()) {
2712 case ax::mojom::blink::Role::kButton:
2713 case ax::mojom::blink::Role::kDisclosureTriangle:
2714 case ax::mojom::blink::Role::kToggleButton:
2715 return ax::mojom::blink::DefaultActionVerb::kPress;
2716 case ax::mojom::blink::Role::kListBoxOption:
2717 case ax::mojom::blink::Role::kMenuItemRadio:
2718 case ax::mojom::blink::Role::kMenuItem:
2719 case ax::mojom::blink::Role::kMenuListOption:
2720 return ax::mojom::blink::DefaultActionVerb::kSelect;
2721 case ax::mojom::blink::Role::kLink:
2722 return ax::mojom::blink::DefaultActionVerb::kJump;
2723 case ax::mojom::blink::Role::kComboBoxMenuButton:
2724 case ax::mojom::blink::Role::kPopUpButton:
2725 return ax::mojom::blink::DefaultActionVerb::kOpen;
2726 default:
2727 if (action_element == GetNode())
2728 return ax::mojom::blink::DefaultActionVerb::kClick;
2729 return ax::mojom::blink::DefaultActionVerb::kClickAncestor;
2730 }
2731 }
2732
AriaPressedIsPresent() const2733 bool AXObject::AriaPressedIsPresent() const {
2734 AtomicString result;
2735 return HasAOMPropertyOrARIAAttribute(AOMStringProperty::kPressed, result);
2736 }
2737
AriaCheckedIsPresent() const2738 bool AXObject::AriaCheckedIsPresent() const {
2739 AtomicString result;
2740 return HasAOMPropertyOrARIAAttribute(AOMStringProperty::kChecked, result);
2741 }
2742
SupportsARIAExpanded() const2743 bool AXObject::SupportsARIAExpanded() const {
2744 switch (RoleValue()) {
2745 case ax::mojom::blink::Role::kApplication:
2746 case ax::mojom::blink::Role::kButton:
2747 case ax::mojom::blink::Role::kCheckBox:
2748 case ax::mojom::blink::Role::kColumnHeader:
2749 case ax::mojom::blink::Role::kComboBoxGrouping:
2750 case ax::mojom::blink::Role::kComboBoxMenuButton:
2751 case ax::mojom::blink::Role::kDisclosureTriangle:
2752 case ax::mojom::blink::Role::kListBox:
2753 case ax::mojom::blink::Role::kLink:
2754 case ax::mojom::blink::Role::kPopUpButton:
2755 case ax::mojom::blink::Role::kMenuItem:
2756 case ax::mojom::blink::Role::kMenuItemCheckBox:
2757 case ax::mojom::blink::Role::kMenuItemRadio:
2758 case ax::mojom::blink::Role::kRow:
2759 case ax::mojom::blink::Role::kRowHeader:
2760 case ax::mojom::blink::Role::kSwitch:
2761 case ax::mojom::blink::Role::kTab:
2762 case ax::mojom::blink::Role::kTextFieldWithComboBox:
2763 case ax::mojom::blink::Role::kTreeItem:
2764 return true;
2765 case ax::mojom::blink::Role::kCell:
2766 // TODO(Accessibility): aria-expanded is supported on grid cells but not
2767 // on cells inside a static table. Consider creating separate internal
2768 // roles so that we can easily distinguish these two types. See also
2769 // IsSubWidget().
2770 return true;
2771 default:
2772 return false;
2773 }
2774 }
2775
IsGlobalARIAAttribute(const AtomicString & name)2776 bool IsGlobalARIAAttribute(const AtomicString& name) {
2777 if (!name.StartsWith("ARIA"))
2778 return false;
2779 if (name.StartsWith("ARIA-ATOMIC"))
2780 return true;
2781 if (name.StartsWith("ARIA-BUSY"))
2782 return true;
2783 if (name.StartsWith("ARIA-CONTROLS"))
2784 return true;
2785 if (name.StartsWith("ARIA-CURRENT"))
2786 return true;
2787 if (name.StartsWith("ARIA-DESCRIBEDBY"))
2788 return true;
2789 if (name.StartsWith("ARIA-DETAILS"))
2790 return true;
2791 if (name.StartsWith("ARIA-DISABLED"))
2792 return true;
2793 if (name.StartsWith("ARIA-DROPEFFECT"))
2794 return true;
2795 if (name.StartsWith("ARIA-ERRORMESSAGE"))
2796 return true;
2797 if (name.StartsWith("ARIA-FLOWTO"))
2798 return true;
2799 if (name.StartsWith("ARIA-GRABBED"))
2800 return true;
2801 if (name.StartsWith("ARIA-HASPOPUP"))
2802 return true;
2803 if (name.StartsWith("ARIA-HIDDEN"))
2804 return true;
2805 if (name.StartsWith("ARIA-INVALID"))
2806 return true;
2807 if (name.StartsWith("ARIA-KEYSHORTCUTS"))
2808 return true;
2809 if (name.StartsWith("ARIA-LABEL"))
2810 return true;
2811 if (name.StartsWith("ARIA-LABELEDBY"))
2812 return true;
2813 if (name.StartsWith("ARIA-LABELLEDBY"))
2814 return true;
2815 if (name.StartsWith("ARIA-LIVE"))
2816 return true;
2817 if (name.StartsWith("ARIA-OWNS"))
2818 return true;
2819 if (name.StartsWith("ARIA-RELEVANT"))
2820 return true;
2821 if (name.StartsWith("ARIA-ROLEDESCRIPTION"))
2822 return true;
2823 return false;
2824 }
2825
HasGlobalARIAAttribute() const2826 bool AXObject::HasGlobalARIAAttribute() const {
2827 auto* element = GetElement();
2828 if (!element)
2829 return false;
2830
2831 AttributeCollection attributes = element->AttributesWithoutUpdate();
2832 for (const Attribute& attr : attributes) {
2833 // Attributes cache their uppercase names.
2834 auto name = attr.GetName().LocalNameUpper();
2835 if (IsGlobalARIAAttribute(name))
2836 return true;
2837 }
2838 if (!element->DidAttachInternals())
2839 return false;
2840 const auto& internals_attributes =
2841 element->EnsureElementInternals().GetAttributes();
2842 for (const QualifiedName& attr : internals_attributes.Keys()) {
2843 if (IsGlobalARIAAttribute(attr.LocalNameUpper()))
2844 return true;
2845 }
2846 return false;
2847 }
2848
IndexInParent() const2849 int AXObject::IndexInParent() const {
2850 DCHECK(AccessibilityIsIncludedInTree())
2851 << "IndexInParent is only valid when a node is included in the tree";
2852 if (!ParentObjectIncludedInTree())
2853 return 0;
2854
2855 const AXObjectVector& siblings =
2856 ParentObjectIncludedInTree()->ChildrenIncludingIgnored();
2857 wtf_size_t index = siblings.Find(this);
2858 DCHECK_NE(index, kNotFound);
2859 return (index == kNotFound) ? 0 : static_cast<int>(index);
2860 }
2861
IsLiveRegionRoot() const2862 bool AXObject::IsLiveRegionRoot() const {
2863 const AtomicString& live_region = LiveRegionStatus();
2864 return !live_region.IsEmpty();
2865 }
2866
IsActiveLiveRegionRoot() const2867 bool AXObject::IsActiveLiveRegionRoot() const {
2868 const AtomicString& live_region = LiveRegionStatus();
2869 return !live_region.IsEmpty() && !EqualIgnoringASCIICase(live_region, "off");
2870 }
2871
Restriction() const2872 AXRestriction AXObject::Restriction() const {
2873 // According to ARIA, all elements of the base markup can be disabled.
2874 // According to CORE-AAM, any focusable descendant of aria-disabled
2875 // ancestor is also disabled.
2876 bool is_disabled;
2877 if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kDisabled,
2878 is_disabled)) {
2879 // Has aria-disabled, overrides native markup determining disabled.
2880 if (is_disabled)
2881 return kRestrictionDisabled;
2882 } else if (CanSetFocusAttribute() && IsDescendantOfDisabledNode()) {
2883 // aria-disabled on an ancestor propagates to focusable descendants.
2884 return kRestrictionDisabled;
2885 }
2886
2887 // Check aria-readonly if supported by current role.
2888 bool is_read_only;
2889 if (SupportsARIAReadOnly() &&
2890 HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kReadOnly,
2891 is_read_only)) {
2892 // ARIA overrides other readonly state markup.
2893 return is_read_only ? kRestrictionReadOnly : kRestrictionNone;
2894 }
2895
2896 // This is a node that is not readonly and not disabled.
2897 return kRestrictionNone;
2898 }
2899
DetermineAccessibilityRole()2900 ax::mojom::blink::Role AXObject::DetermineAccessibilityRole() {
2901 aria_role_ = DetermineAriaRoleAttribute();
2902 return aria_role_;
2903 }
2904
AriaRoleAttribute() const2905 ax::mojom::blink::Role AXObject::AriaRoleAttribute() const {
2906 return aria_role_;
2907 }
2908
DetermineAriaRoleAttribute() const2909 ax::mojom::blink::Role AXObject::DetermineAriaRoleAttribute() const {
2910 const AtomicString& aria_role =
2911 GetAOMPropertyOrARIAAttribute(AOMStringProperty::kRole);
2912 if (aria_role.IsNull() || aria_role.IsEmpty())
2913 return ax::mojom::blink::Role::kUnknown;
2914
2915 ax::mojom::blink::Role role = AriaRoleToWebCoreRole(aria_role);
2916
2917 // ARIA states if an item can get focus, it should not be presentational.
2918 // It also states user agents should ignore the presentational role if
2919 // the element has global ARIA states and properties.
2920 if ((role == ax::mojom::blink::Role::kNone ||
2921 role == ax::mojom::blink::Role::kPresentational) &&
2922 (CanSetFocusAttribute() || HasGlobalARIAAttribute()))
2923 return ax::mojom::blink::Role::kUnknown;
2924
2925 if (role == ax::mojom::blink::Role::kButton)
2926 role = ButtonRoleType();
2927
2928 role = RemapAriaRoleDueToParent(role);
2929
2930 // Distinguish between different uses of the "combobox" role:
2931 //
2932 // ax::mojom::blink::Role::kComboBoxGrouping:
2933 // <div role="combobox"><input></div>
2934 // ax::mojom::blink::Role::kTextFieldWithComboBox:
2935 // <input role="combobox">
2936 // ax::mojom::blink::Role::kComboBoxMenuButton:
2937 // <div tabindex=0 role="combobox">Select</div>
2938 if (role == ax::mojom::blink::Role::kComboBoxGrouping) {
2939 if (IsNativeTextControl())
2940 role = ax::mojom::blink::Role::kTextFieldWithComboBox;
2941 else if (GetElement() && GetElement()->SupportsFocus())
2942 role = ax::mojom::blink::Role::kComboBoxMenuButton;
2943 }
2944
2945 if (role != ax::mojom::blink::Role::kUnknown)
2946 return role;
2947
2948 return ax::mojom::blink::Role::kUnknown;
2949 }
2950
RemapAriaRoleDueToParent(ax::mojom::blink::Role role) const2951 ax::mojom::blink::Role AXObject::RemapAriaRoleDueToParent(
2952 ax::mojom::blink::Role role) const {
2953 // Some objects change their role based on their parent.
2954 // However, asking for the unignoredParent calls accessibilityIsIgnored(),
2955 // which can trigger a loop. While inside the call stack of creating an
2956 // element, we need to avoid accessibilityIsIgnored().
2957 // https://bugs.webkit.org/show_bug.cgi?id=65174
2958
2959 // Don't return table roles unless inside a table-like container.
2960 switch (role) {
2961 case ax::mojom::blink::Role::kRow:
2962 case ax::mojom::blink::Role::kRowGroup:
2963 case ax::mojom::blink::Role::kCell:
2964 case ax::mojom::blink::Role::kRowHeader:
2965 case ax::mojom::blink::Role::kColumnHeader:
2966 for (AXObject* ancestor = ParentObjectUnignored(); ancestor;
2967 ancestor = ancestor->ParentObjectUnignored()) {
2968 ax::mojom::blink::Role ancestor_aria_role =
2969 ancestor->AriaRoleAttribute();
2970 if (ancestor_aria_role == ax::mojom::blink::Role::kCell)
2971 return ax::mojom::blink::Role::kGenericContainer; // In another cell,
2972 // illegal.
2973 if (ancestor->IsTableLikeRole())
2974 return role; // Inside a table: ARIA role is legal.
2975 }
2976 return ax::mojom::blink::Role::kGenericContainer; // Not in a table.
2977 default:
2978 break;
2979 }
2980
2981 if (role != ax::mojom::blink::Role::kListBoxOption &&
2982 role != ax::mojom::blink::Role::kMenuItem)
2983 return role;
2984
2985 for (AXObject* parent = ParentObject();
2986 parent && !parent->AccessibilityIsIgnored();
2987 parent = parent->ParentObject()) {
2988 ax::mojom::blink::Role parent_aria_role = parent->AriaRoleAttribute();
2989
2990 // Selects and listboxes both have options as child roles, but they map to
2991 // different roles within WebCore.
2992 if (role == ax::mojom::blink::Role::kListBoxOption &&
2993 parent_aria_role == ax::mojom::blink::Role::kMenu)
2994 return ax::mojom::blink::Role::kMenuItem;
2995
2996 // If the parent had a different role, then we don't need to continue
2997 // searching up the chain.
2998 if (parent_aria_role != ax::mojom::blink::Role::kUnknown)
2999 break;
3000 }
3001
3002 return role;
3003 }
3004
IsEditableRoot() const3005 bool AXObject::IsEditableRoot() const {
3006 UpdateCachedAttributeValuesIfNeeded();
3007 return cached_is_editable_root_;
3008 }
3009
LiveRegionRoot() const3010 AXObject* AXObject::LiveRegionRoot() const {
3011 UpdateCachedAttributeValuesIfNeeded();
3012 return cached_live_region_root_;
3013 }
3014
LiveRegionAtomic() const3015 bool AXObject::LiveRegionAtomic() const {
3016 bool atomic = false;
3017 if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kAtomic, atomic))
3018 return atomic;
3019
3020 // ARIA roles "alert" and "status" should have an implicit aria-atomic value
3021 // of true.
3022 return RoleValue() == ax::mojom::blink::Role::kAlert ||
3023 RoleValue() == ax::mojom::blink::Role::kStatus;
3024 }
3025
ContainerLiveRegionStatus() const3026 const AtomicString& AXObject::ContainerLiveRegionStatus() const {
3027 UpdateCachedAttributeValuesIfNeeded();
3028 return cached_live_region_root_ ? cached_live_region_root_->LiveRegionStatus()
3029 : g_null_atom;
3030 }
3031
ContainerLiveRegionRelevant() const3032 const AtomicString& AXObject::ContainerLiveRegionRelevant() const {
3033 UpdateCachedAttributeValuesIfNeeded();
3034 return cached_live_region_root_
3035 ? cached_live_region_root_->LiveRegionRelevant()
3036 : g_null_atom;
3037 }
3038
ContainerLiveRegionAtomic() const3039 bool AXObject::ContainerLiveRegionAtomic() const {
3040 UpdateCachedAttributeValuesIfNeeded();
3041 return cached_live_region_root_ &&
3042 cached_live_region_root_->LiveRegionAtomic();
3043 }
3044
ContainerLiveRegionBusy() const3045 bool AXObject::ContainerLiveRegionBusy() const {
3046 UpdateCachedAttributeValuesIfNeeded();
3047 return cached_live_region_root_ &&
3048 cached_live_region_root_->AOMPropertyOrARIAAttributeIsTrue(
3049 AOMBooleanProperty::kBusy);
3050 }
3051
ElementAccessibilityHitTest(const IntPoint & point) const3052 AXObject* AXObject::ElementAccessibilityHitTest(const IntPoint& point) const {
3053 // Check if there are any mock elements that need to be handled.
3054 for (const auto& child : ChildrenIncludingIgnored()) {
3055 if (child->IsMockObject() &&
3056 child->GetBoundsInFrameCoordinates().Contains(point))
3057 return child->ElementAccessibilityHitTest(point);
3058 }
3059
3060 return const_cast<AXObject*>(this);
3061 }
3062
UnignoredAncestorsBegin() const3063 AXObject::AncestorsIterator AXObject::UnignoredAncestorsBegin() const {
3064 AXObject* parent = ParentObjectUnignored();
3065 if (parent)
3066 return AXObject::AncestorsIterator(*parent);
3067 return UnignoredAncestorsEnd();
3068 }
3069
UnignoredAncestorsEnd() const3070 AXObject::AncestorsIterator AXObject::UnignoredAncestorsEnd() const {
3071 return AXObject::AncestorsIterator();
3072 }
3073
GetInOrderTraversalIterator()3074 AXObject::InOrderTraversalIterator AXObject::GetInOrderTraversalIterator() {
3075 return InOrderTraversalIterator(*this);
3076 }
3077
ChildCountIncludingIgnored() const3078 int AXObject::ChildCountIncludingIgnored() const {
3079 return HasIndirectChildren() ? 0 : int{ChildrenIncludingIgnored().size()};
3080 }
3081
ChildAtIncludingIgnored(int index) const3082 AXObject* AXObject::ChildAtIncludingIgnored(int index) const {
3083 // We need to use "ChildCountIncludingIgnored()" and
3084 // "ChildrenIncludingIgnored()" instead of using the "children_" member
3085 // directly, because we might need to update children and check for the
3086 // presence of indirect children.
3087 if (index < 0 || index >= ChildCountIncludingIgnored())
3088 return nullptr;
3089 return ChildrenIncludingIgnored()[index];
3090 }
3091
ChildrenIncludingIgnored() const3092 const AXObject::AXObjectVector& AXObject::ChildrenIncludingIgnored() const {
3093 return const_cast<AXObject*>(this)->ChildrenIncludingIgnored();
3094 }
3095
ChildrenIncludingIgnored()3096 const AXObject::AXObjectVector& AXObject::ChildrenIncludingIgnored() {
3097 UpdateChildrenIfNecessary();
3098 return children_;
3099 }
3100
UnignoredChildren() const3101 const AXObject::AXObjectVector AXObject::UnignoredChildren() const {
3102 return const_cast<AXObject*>(this)->UnignoredChildren();
3103 }
3104
UnignoredChildren()3105 const AXObject::AXObjectVector AXObject::UnignoredChildren() {
3106 if (!AccessibilityIsIncludedInTree()) {
3107 NOTREACHED() << "We don't support finding the unignored children of "
3108 "objects excluded from the accessibility tree.";
3109 return {};
3110 }
3111
3112 UpdateChildrenIfNecessary();
3113
3114 // Capture only descendants that are not accessibility ignored, and that are
3115 // one level deeper than the current object after flattening any accessibility
3116 // ignored descendants.
3117 //
3118 // For example :
3119 // ++A
3120 // ++++B
3121 // ++++C IGNORED
3122 // ++++++F
3123 // ++++D
3124 // ++++++G
3125 // ++++E IGNORED
3126 // ++++++H IGNORED
3127 // ++++++++J
3128 // ++++++I
3129 //
3130 // Objects [B, F, D, I, J] will be returned, since after flattening all
3131 // ignored objects ,those are the ones that are one level deep.
3132
3133 AXObjectVector unignored_children;
3134 AXObject* child = FirstChildIncludingIgnored();
3135 while (child && child != this) {
3136 if (child->AccessibilityIsIgnored()) {
3137 child = child->NextInPreOrderIncludingIgnored(this);
3138 continue;
3139 }
3140
3141 unignored_children.push_back(child);
3142 for (; child != this; child = child->ParentObjectIncludedInTree()) {
3143 if (AXObject* sibling = child->NextSiblingIncludingIgnored()) {
3144 child = sibling;
3145 break;
3146 }
3147 }
3148 }
3149
3150 return unignored_children;
3151 }
3152
FirstChildIncludingIgnored() const3153 AXObject* AXObject::FirstChildIncludingIgnored() const {
3154 return ChildCountIncludingIgnored() ? *ChildrenIncludingIgnored().begin()
3155 : nullptr;
3156 }
3157
LastChildIncludingIgnored() const3158 AXObject* AXObject::LastChildIncludingIgnored() const {
3159 return ChildCountIncludingIgnored() ? *(ChildrenIncludingIgnored().end() - 1)
3160 : nullptr;
3161 }
3162
DeepestFirstChildIncludingIgnored() const3163 AXObject* AXObject::DeepestFirstChildIncludingIgnored() const {
3164 if (!ChildCountIncludingIgnored())
3165 return nullptr;
3166
3167 AXObject* deepest_child = FirstChildIncludingIgnored();
3168 while (deepest_child->ChildCountIncludingIgnored())
3169 deepest_child = deepest_child->FirstChildIncludingIgnored();
3170
3171 return deepest_child;
3172 }
3173
DeepestLastChildIncludingIgnored() const3174 AXObject* AXObject::DeepestLastChildIncludingIgnored() const {
3175 if (!ChildCountIncludingIgnored())
3176 return nullptr;
3177
3178 AXObject* deepest_child = LastChildIncludingIgnored();
3179 while (deepest_child->ChildCountIncludingIgnored())
3180 deepest_child = deepest_child->LastChildIncludingIgnored();
3181
3182 return deepest_child;
3183 }
3184
IsAncestorOf(const AXObject & descendant) const3185 bool AXObject::IsAncestorOf(const AXObject& descendant) const {
3186 return descendant.IsDescendantOf(*this);
3187 }
3188
IsDescendantOf(const AXObject & ancestor) const3189 bool AXObject::IsDescendantOf(const AXObject& ancestor) const {
3190 const AXObject* parent = ParentObject();
3191 while (parent && parent != &ancestor)
3192 parent = parent->ParentObject();
3193 return !!parent;
3194 }
3195
NextSiblingIncludingIgnored() const3196 AXObject* AXObject::NextSiblingIncludingIgnored() const {
3197 if (!AccessibilityIsIncludedInTree()) {
3198 NOTREACHED() << "We don't support iterating over objects excluded "
3199 "from the accessibility tree.";
3200 return nullptr;
3201 }
3202
3203 const AXObject* parent_in_tree = ParentObjectIncludedInTree();
3204 if (!parent_in_tree)
3205 return nullptr;
3206
3207 const int index_in_parent = IndexInParent();
3208 if (index_in_parent < parent_in_tree->ChildCountIncludingIgnored() - 1)
3209 return parent_in_tree->ChildAtIncludingIgnored(index_in_parent + 1);
3210 return nullptr;
3211 }
3212
PreviousSiblingIncludingIgnored() const3213 AXObject* AXObject::PreviousSiblingIncludingIgnored() const {
3214 if (!AccessibilityIsIncludedInTree()) {
3215 NOTREACHED() << "We don't support iterating over objects excluded "
3216 "from the accessibility tree.";
3217 return nullptr;
3218 }
3219
3220 const AXObject* parent_in_tree = ParentObjectIncludedInTree();
3221 if (!parent_in_tree)
3222 return nullptr;
3223
3224 const int index_in_parent = IndexInParent();
3225 if (index_in_parent > 0)
3226 return parent_in_tree->ChildAtIncludingIgnored(index_in_parent - 1);
3227 return nullptr;
3228 }
3229
NextInPreOrderIncludingIgnored(const AXObject * within) const3230 AXObject* AXObject::NextInPreOrderIncludingIgnored(
3231 const AXObject* within) const {
3232 if (!AccessibilityIsIncludedInTree()) {
3233 NOTREACHED() << "We don't support iterating over objects excluded "
3234 "from the accessibility tree.";
3235 return nullptr;
3236 }
3237
3238 if (ChildCountIncludingIgnored())
3239 return FirstChildIncludingIgnored();
3240
3241 if (within == this)
3242 return nullptr;
3243
3244 const AXObject* current = this;
3245 AXObject* next = current->NextSiblingIncludingIgnored();
3246 for (; !next; next = current->NextSiblingIncludingIgnored()) {
3247 current = current->ParentObjectIncludedInTree();
3248 if (!current || within == current)
3249 return nullptr;
3250 }
3251 return next;
3252 }
3253
PreviousInPreOrderIncludingIgnored(const AXObject * within) const3254 AXObject* AXObject::PreviousInPreOrderIncludingIgnored(
3255 const AXObject* within) const {
3256 if (!AccessibilityIsIncludedInTree()) {
3257 NOTREACHED() << "We don't support iterating over objects excluded "
3258 "from the accessibility tree.";
3259 return nullptr;
3260 }
3261 if (within == this)
3262 return nullptr;
3263
3264 if (AXObject* sibling = PreviousSiblingIncludingIgnored()) {
3265 if (sibling->ChildCountIncludingIgnored())
3266 return sibling->DeepestLastChildIncludingIgnored();
3267 return sibling;
3268 }
3269
3270 return ParentObjectIncludedInTree();
3271 }
3272
PreviousInPostOrderIncludingIgnored(const AXObject * within) const3273 AXObject* AXObject::PreviousInPostOrderIncludingIgnored(
3274 const AXObject* within) const {
3275 if (!AccessibilityIsIncludedInTree()) {
3276 NOTREACHED() << "We don't support iterating over objects excluded "
3277 "from the accessibility tree.";
3278 return nullptr;
3279 }
3280
3281 if (ChildCountIncludingIgnored())
3282 return LastChildIncludingIgnored();
3283
3284 if (within == this)
3285 return nullptr;
3286
3287 const AXObject* current = this;
3288 AXObject* previous = current->PreviousSiblingIncludingIgnored();
3289 for (; !previous; previous = current->PreviousSiblingIncludingIgnored()) {
3290 current = current->ParentObjectIncludedInTree();
3291 if (!current || within == current)
3292 return nullptr;
3293 }
3294 return previous;
3295 }
3296
UnignoredChildCount() const3297 int AXObject::UnignoredChildCount() const {
3298 return int{UnignoredChildren().size()};
3299 }
3300
UnignoredChildAt(int index) const3301 AXObject* AXObject::UnignoredChildAt(int index) const {
3302 const AXObjectVector unignored_children = UnignoredChildren();
3303 if (index < 0 || index >= int{unignored_children.size()})
3304 return nullptr;
3305 return unignored_children[index];
3306 }
3307
UnignoredNextSibling() const3308 AXObject* AXObject::UnignoredNextSibling() const {
3309 if (AccessibilityIsIgnored()) {
3310 NOTREACHED() << "We don't support finding unignored siblings for ignored "
3311 "objects because it is not clear whether to search for the "
3312 "sibling in the unignored tree or in the whole tree.";
3313 return nullptr;
3314 }
3315
3316 // Find the next sibling for the same unignored parent object,
3317 // flattening accessibility ignored objects.
3318 //
3319 // For example :
3320 // ++A
3321 // ++++B
3322 // ++++C IGNORED
3323 // ++++++E
3324 // ++++D
3325 // Objects [B, E, D] will be siblings since C is ignored.
3326
3327 const AXObject* unignored_parent = ParentObjectUnignored();
3328 const AXObject* current_obj = this;
3329 while (current_obj) {
3330 AXObject* sibling = current_obj->NextSiblingIncludingIgnored();
3331 if (sibling) {
3332 // If we found an ignored sibling, walk in next pre-order
3333 // until an unignored object is found, flattening the ignored object.
3334 while (sibling && sibling->AccessibilityIsIgnored()) {
3335 sibling = sibling->NextInPreOrderIncludingIgnored(unignored_parent);
3336 }
3337 return sibling;
3338 }
3339
3340 // If a sibling has not been found, try again with the parent object,
3341 // until the unignored parent is reached.
3342 current_obj = current_obj->ParentObjectIncludedInTree();
3343 if (!current_obj || !current_obj->AccessibilityIsIgnored())
3344 return nullptr;
3345 }
3346 return nullptr;
3347 }
3348
UnignoredPreviousSibling() const3349 AXObject* AXObject::UnignoredPreviousSibling() const {
3350 if (AccessibilityIsIgnored()) {
3351 NOTREACHED() << "We don't support finding unignored siblings for ignored "
3352 "objects because it is not clear whether to search for the "
3353 "sibling in the unignored tree or in the whole tree.";
3354 return nullptr;
3355 }
3356
3357 // Find the previous sibling for the same unignored parent object,
3358 // flattening accessibility ignored objects.
3359 //
3360 // For example :
3361 // ++A
3362 // ++++B
3363 // ++++C IGNORED
3364 // ++++++E
3365 // ++++D
3366 // Objects [B, E, D] will be siblings since C is ignored.
3367
3368 const AXObject* current_obj = this;
3369 while (current_obj) {
3370 AXObject* sibling = current_obj->PreviousSiblingIncludingIgnored();
3371 if (sibling) {
3372 const AXObject* unignored_parent = ParentObjectUnignored();
3373 // If we found an ignored sibling, walk in previous post-order
3374 // until an unignored object is found, flattening the ignored object.
3375 while (sibling && sibling->AccessibilityIsIgnored()) {
3376 sibling =
3377 sibling->PreviousInPostOrderIncludingIgnored(unignored_parent);
3378 }
3379 return sibling;
3380 }
3381
3382 // If a sibling has not been found, try again with the parent object,
3383 // until the unignored parent is reached.
3384 current_obj = current_obj->ParentObjectIncludedInTree();
3385 if (!current_obj || !current_obj->AccessibilityIsIgnored())
3386 return nullptr;
3387 }
3388 return nullptr;
3389 }
3390
UnignoredNextInPreOrder() const3391 AXObject* AXObject::UnignoredNextInPreOrder() const {
3392 AXObject* next = NextInPreOrderIncludingIgnored();
3393 while (next && next->AccessibilityIsIgnored()) {
3394 next = next->NextInPreOrderIncludingIgnored();
3395 }
3396 return next;
3397 }
3398
UnignoredPreviousInPreOrder() const3399 AXObject* AXObject::UnignoredPreviousInPreOrder() const {
3400 AXObject* previous = PreviousInPreOrderIncludingIgnored();
3401 while (previous && previous->AccessibilityIsIgnored()) {
3402 previous = previous->PreviousInPreOrderIncludingIgnored();
3403 }
3404 return previous;
3405 }
3406
ParentObject() const3407 AXObject* AXObject::ParentObject() const {
3408 if (IsDetached())
3409 return nullptr;
3410
3411 if (parent_)
3412 return parent_;
3413
3414 if (AXObjectCache().IsAriaOwned(this))
3415 return AXObjectCache().GetAriaOwnedParent(this);
3416
3417 return ComputeParent();
3418 }
3419
ParentObjectIfExists() const3420 AXObject* AXObject::ParentObjectIfExists() const {
3421 if (IsDetached())
3422 return nullptr;
3423
3424 if (parent_)
3425 return parent_;
3426
3427 return ComputeParentIfExists();
3428 }
3429
ParentObjectUnignored() const3430 AXObject* AXObject::ParentObjectUnignored() const {
3431 AXObject* parent;
3432 for (parent = ParentObject(); parent && parent->AccessibilityIsIgnored();
3433 parent = parent->ParentObject()) {
3434 }
3435
3436 return parent;
3437 }
3438
ParentObjectIncludedInTree() const3439 AXObject* AXObject::ParentObjectIncludedInTree() const {
3440 AXObject* parent;
3441 for (parent = ParentObject();
3442 parent && !parent->AccessibilityIsIncludedInTree();
3443 parent = parent->ParentObject()) {
3444 }
3445
3446 return parent;
3447 }
3448
3449 // Container widgets are those that a user tabs into and arrows around
3450 // sub-widgets
IsContainerWidget() const3451 bool AXObject::IsContainerWidget() const {
3452 return ui::IsContainerWithSelectableChildren(RoleValue());
3453 }
3454
ContainerWidget() const3455 AXObject* AXObject::ContainerWidget() const {
3456 AXObject* ancestor = ParentObjectUnignored();
3457 while (ancestor && !ancestor->IsContainerWidget())
3458 ancestor = ancestor->ParentObjectUnignored();
3459
3460 return ancestor;
3461 }
3462
UpdateChildrenIfNecessary()3463 void AXObject::UpdateChildrenIfNecessary() {
3464 if (!HasChildren())
3465 AddChildren();
3466 }
3467
ClearChildren()3468 void AXObject::ClearChildren() {
3469 // Detach all weak pointers from objects to their parents.
3470 for (const auto& child : children_) {
3471 if (child->parent_ == this)
3472 child->DetachFromParent();
3473 }
3474
3475 children_.clear();
3476 have_children_ = false;
3477 }
3478
AddAccessibleNodeChildren()3479 void AXObject::AddAccessibleNodeChildren() {
3480 Element* element = GetElement();
3481 if (!element)
3482 return;
3483
3484 AccessibleNode* accessible_node = element->ExistingAccessibleNode();
3485 if (!accessible_node)
3486 return;
3487
3488 for (const auto& child : accessible_node->GetChildren())
3489 children_.push_back(AXObjectCache().GetOrCreate(child));
3490 }
3491
GetElement() const3492 Element* AXObject::GetElement() const {
3493 return DynamicTo<Element>(GetNode());
3494 }
3495
GetDocument() const3496 Document* AXObject::GetDocument() const {
3497 LocalFrameView* frame_view = DocumentFrameView();
3498 if (!frame_view)
3499 return nullptr;
3500
3501 return frame_view->GetFrame().GetDocument();
3502 }
3503
RootScroller() const3504 AXObject* AXObject::RootScroller() const {
3505 Node* global_root_scroller = GetDocument()
3506 ->GetPage()
3507 ->GlobalRootScrollerController()
3508 .GlobalRootScroller();
3509 if (!global_root_scroller)
3510 return nullptr;
3511
3512 // Only return the root scroller if it's part of the same document.
3513 if (global_root_scroller->GetDocument() != GetDocument())
3514 return nullptr;
3515
3516 return AXObjectCache().GetOrCreate(global_root_scroller);
3517 }
3518
DocumentFrameView() const3519 LocalFrameView* AXObject::DocumentFrameView() const {
3520 const AXObject* object = this;
3521 while (object && !object->IsAXLayoutObject())
3522 object = object->ParentObject();
3523
3524 if (!object)
3525 return nullptr;
3526
3527 return object->DocumentFrameView();
3528 }
3529
Language() const3530 AtomicString AXObject::Language() const {
3531 // This method is used when the style engine is either not available on this
3532 // object, e.g. for canvas fallback content, or is unable to determine the
3533 // document's language. We use the following signals to detect the element's
3534 // language, in decreasing priority:
3535 // 1. The [language of a node] as defined in HTML, if known.
3536 // 2. The list of languages the browser sends in the [Accept-Language] header.
3537 // 3. The browser's default language.
3538
3539 const AtomicString& lang = GetAttribute(html_names::kLangAttr);
3540 if (!lang.IsEmpty())
3541 return lang;
3542
3543 // Only fallback for the root node, propagating this value down the tree is
3544 // handled browser side within AXNode::GetLanguage.
3545 //
3546 // TODO(chrishall): Consider moving this to AXNodeObject or AXLayoutObject as
3547 // the kRootWebArea node is currently an AXLayoutObject.
3548 if (RoleValue() == ax::mojom::blink::Role::kRootWebArea) {
3549 const Document* document = GetDocument();
3550 if (document) {
3551 // Fall back to the first content language specified in the meta tag.
3552 // This is not part of what the HTML5 Standard suggests but it still
3553 // appears to be necessary.
3554 if (document->ContentLanguage()) {
3555 const String content_languages = document->ContentLanguage();
3556 Vector<String> languages;
3557 content_languages.Split(',', languages);
3558 if (!languages.IsEmpty())
3559 return AtomicString(languages[0].StripWhiteSpace());
3560 }
3561
3562 if (document->GetPage()) {
3563 // Use the first accept language preference if present.
3564 const String accept_languages =
3565 document->GetPage()->GetChromeClient().AcceptLanguages();
3566 Vector<String> languages;
3567 accept_languages.Split(',', languages);
3568 if (!languages.IsEmpty())
3569 return AtomicString(languages[0].StripWhiteSpace());
3570 }
3571 }
3572
3573 // As a last resort, return the default language of the browser's UI.
3574 AtomicString default_language = DefaultLanguage();
3575 return default_language;
3576 }
3577
3578 return g_null_atom;
3579 }
3580
3581 //
3582 // Scrollable containers.
3583 //
3584
IsScrollableContainer() const3585 bool AXObject::IsScrollableContainer() const {
3586 return !!GetScrollableAreaIfScrollable();
3587 }
3588
IsUserScrollable() const3589 bool AXObject::IsUserScrollable() const {
3590 // TODO(accessibility) Actually expose correct info on whether a doc is
3591 // is scrollable or not. Unfortunately IsScrollableContainer() always returns
3592 // true anyway. For now, just expose as scrollable unless overflow is hidden.
3593 if (IsWebArea()) {
3594 if (!GetScrollableAreaIfScrollable() || !GetLayoutObject())
3595 return false;
3596
3597 const ComputedStyle* style = GetLayoutObject()->Style();
3598 if (!style)
3599 return false;
3600
3601 return style->ScrollsOverflowY() || style->ScrollsOverflowX();
3602 }
3603
3604 return GetLayoutObject() && GetLayoutObject()->IsBox() &&
3605 To<LayoutBox>(GetLayoutObject())->CanBeScrolledAndHasScrollableArea();
3606 }
3607
GetScrollOffset() const3608 IntPoint AXObject::GetScrollOffset() const {
3609 ScrollableArea* area = GetScrollableAreaIfScrollable();
3610 if (!area)
3611 return IntPoint();
3612
3613 return IntPoint(area->ScrollOffsetInt().Width(),
3614 area->ScrollOffsetInt().Height());
3615 }
3616
MinimumScrollOffset() const3617 IntPoint AXObject::MinimumScrollOffset() const {
3618 ScrollableArea* area = GetScrollableAreaIfScrollable();
3619 if (!area)
3620 return IntPoint();
3621
3622 return IntPoint(area->MinimumScrollOffsetInt().Width(),
3623 area->MinimumScrollOffsetInt().Height());
3624 }
3625
MaximumScrollOffset() const3626 IntPoint AXObject::MaximumScrollOffset() const {
3627 ScrollableArea* area = GetScrollableAreaIfScrollable();
3628 if (!area)
3629 return IntPoint();
3630
3631 return IntPoint(area->MaximumScrollOffsetInt().Width(),
3632 area->MaximumScrollOffsetInt().Height());
3633 }
3634
SetScrollOffset(const IntPoint & offset) const3635 void AXObject::SetScrollOffset(const IntPoint& offset) const {
3636 ScrollableArea* area = GetScrollableAreaIfScrollable();
3637 if (!area)
3638 return;
3639
3640 // TODO(bokan): This should potentially be a UserScroll.
3641 area->SetScrollOffset(ScrollOffset(offset.X(), offset.Y()),
3642 mojom::blink::ScrollType::kProgrammatic);
3643 }
3644
IsTableLikeRole() const3645 bool AXObject::IsTableLikeRole() const {
3646 return ui::IsTableLike(RoleValue()) ||
3647 RoleValue() == ax::mojom::blink::Role::kLayoutTable;
3648 }
3649
IsTableRowLikeRole() const3650 bool AXObject::IsTableRowLikeRole() const {
3651 return ui::IsTableRow(RoleValue()) ||
3652 RoleValue() == ax::mojom::blink::Role::kLayoutTableRow;
3653 }
3654
IsTableCellLikeRole() const3655 bool AXObject::IsTableCellLikeRole() const {
3656 return ui::IsCellOrTableHeader(RoleValue()) ||
3657 RoleValue() == ax::mojom::blink::Role::kLayoutTableCell;
3658 }
3659
ColumnCount() const3660 unsigned AXObject::ColumnCount() const {
3661 if (!IsTableLikeRole())
3662 return 0;
3663
3664 unsigned max_column_count = 0;
3665 for (const auto& row : TableRowChildren()) {
3666 unsigned column_count = row->TableCellChildren().size();
3667 max_column_count = std::max(column_count, max_column_count);
3668 }
3669
3670 return max_column_count;
3671 }
3672
RowCount() const3673 unsigned AXObject::RowCount() const {
3674 if (!IsTableLikeRole())
3675 return 0;
3676
3677 return TableRowChildren().size();
3678 }
3679
ColumnHeaders(AXObjectVector & headers) const3680 void AXObject::ColumnHeaders(AXObjectVector& headers) const {
3681 if (!IsTableLikeRole())
3682 return;
3683
3684 for (const auto& row : TableRowChildren()) {
3685 for (const auto& cell : row->TableCellChildren()) {
3686 if (cell->RoleValue() == ax::mojom::blink::Role::kColumnHeader)
3687 headers.push_back(cell);
3688 }
3689 }
3690 }
3691
RowHeaders(AXObjectVector & headers) const3692 void AXObject::RowHeaders(AXObjectVector& headers) const {
3693 if (!IsTableLikeRole())
3694 return;
3695
3696 for (const auto& row : TableRowChildren()) {
3697 for (const auto& cell : row->TableCellChildren()) {
3698 if (cell->RoleValue() == ax::mojom::blink::Role::kRowHeader)
3699 headers.push_back(cell);
3700 }
3701 }
3702 }
3703
CellForColumnAndRow(unsigned target_column_index,unsigned target_row_index) const3704 AXObject* AXObject::CellForColumnAndRow(unsigned target_column_index,
3705 unsigned target_row_index) const {
3706 if (!IsTableLikeRole())
3707 return nullptr;
3708
3709 // Note that this code is only triggered if this is not a LayoutTable,
3710 // i.e. it's an ARIA grid/table.
3711 //
3712 // TODO(dmazzoni): delete this code or rename it "for testing only"
3713 // since it's only needed for Blink web tests and not for production.
3714 unsigned row_index = 0;
3715 for (const auto& row : TableRowChildren()) {
3716 unsigned column_index = 0;
3717 for (const auto& cell : row->TableCellChildren()) {
3718 if (target_column_index == column_index && target_row_index == row_index)
3719 return cell;
3720 column_index++;
3721 }
3722 row_index++;
3723 }
3724
3725 return nullptr;
3726 }
3727
AriaColumnCount() const3728 int AXObject::AriaColumnCount() const {
3729 if (!IsTableLikeRole())
3730 return 0;
3731
3732 int32_t col_count;
3733 if (!HasAOMPropertyOrARIAAttribute(AOMIntProperty::kColCount, col_count))
3734 return 0;
3735
3736 if (col_count > static_cast<int>(ColumnCount()))
3737 return col_count;
3738
3739 // Spec says that if all of the columns are present in the DOM, it
3740 // is not necessary to set this attribute as the user agent can
3741 // automatically calculate the total number of columns.
3742 // It returns 0 in order not to set this attribute.
3743 if (col_count == static_cast<int>(ColumnCount()) || col_count != -1)
3744 return 0;
3745
3746 return -1;
3747 }
3748
AriaRowCount() const3749 int AXObject::AriaRowCount() const {
3750 if (!IsTableLikeRole())
3751 return 0;
3752
3753 int32_t row_count;
3754 if (!HasAOMPropertyOrARIAAttribute(AOMIntProperty::kRowCount, row_count))
3755 return 0;
3756
3757 if (row_count > int{RowCount()})
3758 return row_count;
3759
3760 // Spec says that if all of the rows are present in the DOM, it is
3761 // not necessary to set this attribute as the user agent can
3762 // automatically calculate the total number of rows.
3763 // It returns 0 in order not to set this attribute.
3764 if (row_count == int{RowCount()} || row_count != -1)
3765 return 0;
3766
3767 // In the spec, -1 explicitly means an unknown number of rows.
3768 return -1;
3769 }
3770
ColumnIndex() const3771 unsigned AXObject::ColumnIndex() const {
3772 return 0;
3773 }
3774
RowIndex() const3775 unsigned AXObject::RowIndex() const {
3776 return 0;
3777 }
3778
ColumnSpan() const3779 unsigned AXObject::ColumnSpan() const {
3780 return IsTableCellLikeRole() ? 1 : 0;
3781 }
3782
RowSpan() const3783 unsigned AXObject::RowSpan() const {
3784 return IsTableCellLikeRole() ? 1 : 0;
3785 }
3786
AriaColumnIndex() const3787 unsigned AXObject::AriaColumnIndex() const {
3788 UpdateCachedAttributeValuesIfNeeded();
3789 return cached_aria_column_index_;
3790 }
3791
AriaRowIndex() const3792 unsigned AXObject::AriaRowIndex() const {
3793 UpdateCachedAttributeValuesIfNeeded();
3794 return cached_aria_row_index_;
3795 }
3796
ComputeAriaColumnIndex() const3797 unsigned AXObject::ComputeAriaColumnIndex() const {
3798 // Return the ARIA column index if it has been set. Otherwise return a default
3799 // value of 0.
3800 uint32_t col_index = 0;
3801 HasAOMPropertyOrARIAAttribute(AOMUIntProperty::kColIndex, col_index);
3802 return col_index;
3803 }
3804
ComputeAriaRowIndex() const3805 unsigned AXObject::ComputeAriaRowIndex() const {
3806 // Return the ARIA row index if it has been set. Otherwise return a default
3807 // value of 0.
3808 uint32_t row_index = 0;
3809 HasAOMPropertyOrARIAAttribute(AOMUIntProperty::kRowIndex, row_index);
3810 return row_index;
3811 }
3812
TableRowChildren() const3813 AXObject::AXObjectVector AXObject::TableRowChildren() const {
3814 AXObjectVector result;
3815 for (const auto& child : ChildrenIncludingIgnored()) {
3816 if (child->IsTableRowLikeRole())
3817 result.push_back(child);
3818 else if (child->RoleValue() == ax::mojom::blink::Role::kRowGroup)
3819 result.AppendVector(child->TableRowChildren());
3820 }
3821 return result;
3822 }
3823
TableCellChildren() const3824 AXObject::AXObjectVector AXObject::TableCellChildren() const {
3825 AXObjectVector result;
3826 for (const auto& child : ChildrenIncludingIgnored()) {
3827 if (child->IsTableCellLikeRole())
3828 result.push_back(child);
3829 else if (child->RoleValue() == ax::mojom::blink::Role::kGenericContainer)
3830 result.AppendVector(child->TableCellChildren());
3831 }
3832 return result;
3833 }
3834
TableRowParent() const3835 const AXObject* AXObject::TableRowParent() const {
3836 const AXObject* row = ParentObjectUnignored();
3837 while (row && !row->IsTableRowLikeRole() &&
3838 row->RoleValue() == ax::mojom::blink::Role::kGenericContainer)
3839 row = row->ParentObjectUnignored();
3840 return row;
3841 }
3842
TableParent() const3843 const AXObject* AXObject::TableParent() const {
3844 const AXObject* table = ParentObjectUnignored();
3845 while (table && !table->IsTableLikeRole() &&
3846 table->RoleValue() == ax::mojom::blink::Role::kGenericContainer)
3847 table = table->ParentObjectUnignored();
3848 return table;
3849 }
3850
GetDOMNodeId() const3851 int AXObject::GetDOMNodeId() const {
3852 Node* node = GetNode();
3853 if (node)
3854 return DOMNodeIds::IdForNode(node);
3855 return 0;
3856 }
3857
GetRelativeBounds(AXObject ** out_container,FloatRect & out_bounds_in_container,SkMatrix44 & out_container_transform,bool * clips_children) const3858 void AXObject::GetRelativeBounds(AXObject** out_container,
3859 FloatRect& out_bounds_in_container,
3860 SkMatrix44& out_container_transform,
3861 bool* clips_children) const {
3862 *out_container = nullptr;
3863 out_bounds_in_container = FloatRect();
3864 out_container_transform.setIdentity();
3865
3866 // First check if it has explicit bounds, for example if this element is tied
3867 // to a canvas path. When explicit coordinates are provided, the ID of the
3868 // explicit container element that the coordinates are relative to must be
3869 // provided too.
3870 if (!explicit_element_rect_.IsEmpty()) {
3871 *out_container = AXObjectCache().ObjectFromAXID(explicit_container_id_);
3872 if (*out_container) {
3873 out_bounds_in_container = FloatRect(explicit_element_rect_);
3874 return;
3875 }
3876 }
3877
3878 LayoutObject* layout_object = LayoutObjectForRelativeBounds();
3879 if (!layout_object)
3880 return;
3881
3882 if (layout_object->IsFixedPositioned() ||
3883 layout_object->IsStickyPositioned()) {
3884 AXObjectCache().AddToFixedOrStickyNodeList(this);
3885 }
3886
3887 if (clips_children) {
3888 if (IsWebArea())
3889 *clips_children = true;
3890 else
3891 *clips_children = layout_object->HasNonVisibleOverflow();
3892 }
3893
3894 if (IsWebArea()) {
3895 if (LocalFrameView* view = layout_object->GetFrame()->View()) {
3896 out_bounds_in_container.SetSize(FloatSize(view->Size()));
3897
3898 // If it's a popup, account for the popup window's offset.
3899 if (view->GetPage()->GetChromeClient().IsPopup()) {
3900 IntRect frame_rect = view->FrameToScreen(view->FrameRect());
3901 LocalFrameView* root_view =
3902 AXObjectCache().GetDocument().GetFrame()->View();
3903 IntRect root_frame_rect =
3904 root_view->FrameToScreen(root_view->FrameRect());
3905
3906 // Screen coordinates are in DIP without device scale factor applied.
3907 // Accessibility expects device scale factor applied here which is
3908 // unapplied at the destination AXTree.
3909 float scale_factor =
3910 view->GetPage()->GetChromeClient().WindowToViewportScalar(
3911 layout_object->GetFrame(), 1.0f);
3912 out_bounds_in_container.SetLocation(
3913 FloatPoint(scale_factor * (frame_rect.X() - root_frame_rect.X()),
3914 scale_factor * (frame_rect.Y() - root_frame_rect.Y())));
3915 }
3916 }
3917 return;
3918 }
3919
3920 // First compute the container. The container must be an ancestor in the
3921 // accessibility tree, and its LayoutObject must be an ancestor in the layout
3922 // tree. Get the first such ancestor that's either scrollable or has a paint
3923 // layer.
3924 AXObject* container = ParentObjectUnignored();
3925 LayoutObject* container_layout_object = nullptr;
3926 if (layout_object->IsFixedPositioned()) {
3927 // If it's a fixed position element, the container should simply be the
3928 // root web area.
3929 container = AXObjectCache().GetOrCreate(GetDocument());
3930 } else {
3931 while (container) {
3932 container_layout_object = container->GetLayoutObject();
3933 if (container_layout_object && container_layout_object->IsBox() &&
3934 layout_object->IsDescendantOf(container_layout_object)) {
3935 if (container->IsScrollableContainer() ||
3936 container_layout_object->HasLayer()) {
3937 if (layout_object->IsAbsolutePositioned()) {
3938 // If it's absolutely positioned, the container must be the
3939 // nearest positioned container, or the root.
3940 if (container->IsWebArea())
3941 break;
3942 if (container_layout_object->IsPositioned())
3943 break;
3944 } else {
3945 break;
3946 }
3947 }
3948 }
3949
3950 container = container->ParentObjectUnignored();
3951 }
3952 }
3953
3954 if (!container)
3955 return;
3956 *out_container = container;
3957 out_bounds_in_container =
3958 layout_object->LocalBoundingBoxRectForAccessibility();
3959
3960 // Frames need to take their border and padding into account so the
3961 // child element's computed position will be correct.
3962 if (layout_object->IsBox() && layout_object->GetNode() &&
3963 layout_object->GetNode()->IsFrameOwnerElement()) {
3964 out_bounds_in_container =
3965 FloatRect(To<LayoutBox>(layout_object)->PhysicalContentBoxRect());
3966 }
3967
3968 // If the container has a scroll offset, subtract that out because we want our
3969 // bounds to be relative to the *unscrolled* position of the container object.
3970 if (auto* scrollable_area = container->GetScrollableAreaIfScrollable())
3971 out_bounds_in_container.Move(scrollable_area->GetScrollOffset());
3972
3973 // Compute the transform between the container's coordinate space and this
3974 // object.
3975 TransformationMatrix transform = layout_object->LocalToAncestorTransform(
3976 To<LayoutBoxModelObject>(container_layout_object));
3977
3978 // If the transform is just a simple translation, apply that to the
3979 // bounding box, but if it's a non-trivial transformation like a rotation,
3980 // scaling, etc. then return the full matrix instead.
3981 if (transform.IsIdentityOr2DTranslation()) {
3982 out_bounds_in_container.Move(transform.To2DTranslation());
3983 } else {
3984 out_container_transform = TransformationMatrix::ToSkMatrix44(transform);
3985 }
3986 }
3987
LocalBoundingBoxRectForAccessibility()3988 FloatRect AXObject::LocalBoundingBoxRectForAccessibility() {
3989 if (!GetLayoutObject())
3990 return FloatRect();
3991 DCHECK(GetLayoutObject()->IsText());
3992 UpdateCachedAttributeValuesIfNeeded();
3993 return cached_local_bounding_box_rect_for_accessibility_;
3994 }
3995
GetBoundsInFrameCoordinates() const3996 LayoutRect AXObject::GetBoundsInFrameCoordinates() const {
3997 AXObject* container = nullptr;
3998 FloatRect bounds;
3999 SkMatrix44 transform;
4000 GetRelativeBounds(&container, bounds, transform);
4001 FloatRect computed_bounds(0, 0, bounds.Width(), bounds.Height());
4002 while (container && container != this) {
4003 computed_bounds.Move(bounds.X(), bounds.Y());
4004 if (!container->IsWebArea()) {
4005 computed_bounds.Move(-container->GetScrollOffset().X(),
4006 -container->GetScrollOffset().Y());
4007 }
4008 if (!transform.isIdentity()) {
4009 TransformationMatrix transformation_matrix(transform);
4010 transformation_matrix.MapRect(computed_bounds);
4011 }
4012 container->GetRelativeBounds(&container, bounds, transform);
4013 }
4014 return LayoutRect(computed_bounds);
4015 }
4016
4017 //
4018 // Modify or take an action on an object.
4019 //
4020
RequestDecrementAction()4021 bool AXObject::RequestDecrementAction() {
4022 Event* event =
4023 Event::CreateCancelable(event_type_names::kAccessibledecrement);
4024 if (DispatchEventToAOMEventListeners(*event))
4025 return true;
4026
4027 return OnNativeDecrementAction();
4028 }
4029
RequestClickAction()4030 bool AXObject::RequestClickAction() {
4031 Event* event = Event::CreateCancelable(event_type_names::kAccessibleclick);
4032 if (DispatchEventToAOMEventListeners(*event))
4033 return true;
4034
4035 return OnNativeClickAction();
4036 }
4037
OnNativeClickAction()4038 bool AXObject::OnNativeClickAction() {
4039 Document* document = GetDocument();
4040 if (!document)
4041 return false;
4042
4043 LocalFrame::NotifyUserActivation(
4044 document->GetFrame(),
4045 mojom::blink::UserActivationNotificationType::kInteraction);
4046
4047 Element* element = GetElement();
4048 if (!element && GetNode())
4049 element = GetNode()->parentElement();
4050
4051 if (IsTextControl())
4052 return OnNativeFocusAction();
4053
4054 if (element) {
4055 // Always set the sequential focus navigation starting point.
4056 // Even if this element isn't focusable, if you press "Tab" it will
4057 // start the search from this element.
4058 GetDocument()->SetSequentialFocusNavigationStartingPoint(element);
4059
4060 // Explicitly focus the element if it's focusable but not currently
4061 // the focused element, to be consistent with
4062 // EventHandler::HandleMousePressEvent.
4063 if (element->IsMouseFocusable() && !element->IsFocusedElementInDocument()) {
4064 Page* const page = GetDocument()->GetPage();
4065 if (page) {
4066 page->GetFocusController().SetFocusedElement(
4067 element, GetDocument()->GetFrame(),
4068 FocusParams(SelectionBehaviorOnFocus::kNone,
4069 mojom::blink::FocusType::kMouse, nullptr));
4070 }
4071 }
4072
4073 // For most elements, AccessKeyAction triggers sending a simulated
4074 // click, including simulating the mousedown, mouseup, and click events.
4075 element->AccessKeyAction(true);
4076 return true;
4077 }
4078
4079 if (CanSetFocusAttribute())
4080 return OnNativeFocusAction();
4081
4082 return false;
4083 }
4084
RequestFocusAction()4085 bool AXObject::RequestFocusAction() {
4086 Event* event = Event::CreateCancelable(event_type_names::kAccessiblefocus);
4087 if (DispatchEventToAOMEventListeners(*event))
4088 return true;
4089
4090 return OnNativeFocusAction();
4091 }
4092
RequestIncrementAction()4093 bool AXObject::RequestIncrementAction() {
4094 Event* event =
4095 Event::CreateCancelable(event_type_names::kAccessibleincrement);
4096 if (DispatchEventToAOMEventListeners(*event))
4097 return true;
4098
4099 return OnNativeIncrementAction();
4100 }
4101
RequestScrollToGlobalPointAction(const IntPoint & point)4102 bool AXObject::RequestScrollToGlobalPointAction(const IntPoint& point) {
4103 return OnNativeScrollToGlobalPointAction(point);
4104 }
4105
RequestScrollToMakeVisibleAction()4106 bool AXObject::RequestScrollToMakeVisibleAction() {
4107 Event* event =
4108 Event::CreateCancelable(event_type_names::kAccessiblescrollintoview);
4109 if (DispatchEventToAOMEventListeners(*event))
4110 return true;
4111
4112 return OnNativeScrollToMakeVisibleAction();
4113 }
4114
RequestScrollToMakeVisibleWithSubFocusAction(const IntRect & subfocus,blink::mojom::blink::ScrollAlignment horizontal_scroll_alignment,blink::mojom::blink::ScrollAlignment vertical_scroll_alignment)4115 bool AXObject::RequestScrollToMakeVisibleWithSubFocusAction(
4116 const IntRect& subfocus,
4117 blink::mojom::blink::ScrollAlignment horizontal_scroll_alignment,
4118 blink::mojom::blink::ScrollAlignment vertical_scroll_alignment) {
4119 return OnNativeScrollToMakeVisibleWithSubFocusAction(
4120 subfocus, horizontal_scroll_alignment, vertical_scroll_alignment);
4121 }
4122
RequestSetSelectedAction(bool selected)4123 bool AXObject::RequestSetSelectedAction(bool selected) {
4124 return OnNativeSetSelectedAction(selected);
4125 }
4126
RequestSetSequentialFocusNavigationStartingPointAction()4127 bool AXObject::RequestSetSequentialFocusNavigationStartingPointAction() {
4128 return OnNativeSetSequentialFocusNavigationStartingPointAction();
4129 }
4130
RequestSetValueAction(const String & value)4131 bool AXObject::RequestSetValueAction(const String& value) {
4132 return OnNativeSetValueAction(value);
4133 }
4134
RequestShowContextMenuAction()4135 bool AXObject::RequestShowContextMenuAction() {
4136 Event* event =
4137 Event::CreateCancelable(event_type_names::kAccessiblecontextmenu);
4138 if (DispatchEventToAOMEventListeners(*event))
4139 return true;
4140
4141 return OnNativeShowContextMenuAction();
4142 }
4143
InternalClearAccessibilityFocusAction()4144 bool AXObject::InternalClearAccessibilityFocusAction() {
4145 return false;
4146 }
4147
InternalSetAccessibilityFocusAction()4148 bool AXObject::InternalSetAccessibilityFocusAction() {
4149 return false;
4150 }
4151
GetLayoutObjectForNativeScrollAction() const4152 LayoutObject* AXObject::GetLayoutObjectForNativeScrollAction() const {
4153 Node* node = GetNode();
4154 if (!node || !node->isConnected())
4155 return nullptr;
4156
4157 // Node might not have a LayoutObject due to the fact that it is in a locked
4158 // subtree. Force the update to create the LayoutObject (and update position
4159 // information) for this node.
4160 GetDocument()->UpdateStyleAndLayoutForNode(
4161 node, DocumentUpdateReason::kDisplayLock);
4162 return node->GetLayoutObject();
4163 }
4164
OnNativeScrollToMakeVisibleAction() const4165 bool AXObject::OnNativeScrollToMakeVisibleAction() const {
4166 LayoutObject* layout_object = GetLayoutObjectForNativeScrollAction();
4167 if (!layout_object)
4168 return false;
4169 PhysicalRect target_rect(layout_object->AbsoluteBoundingBoxRect());
4170 layout_object->ScrollRectToVisible(
4171 target_rect,
4172 ScrollAlignment::CreateScrollIntoViewParams(
4173 ScrollAlignment::CenterIfNeeded(), ScrollAlignment::CenterIfNeeded(),
4174 mojom::blink::ScrollType::kProgrammatic, false,
4175 mojom::blink::ScrollBehavior::kAuto));
4176 AXObjectCache().PostNotification(
4177 AXObjectCache().GetOrCreate(GetDocument()->GetLayoutView()),
4178 ax::mojom::blink::Event::kLocationChanged);
4179 return true;
4180 }
4181
OnNativeScrollToMakeVisibleWithSubFocusAction(const IntRect & rect,blink::mojom::blink::ScrollAlignment horizontal_scroll_alignment,blink::mojom::blink::ScrollAlignment vertical_scroll_alignment) const4182 bool AXObject::OnNativeScrollToMakeVisibleWithSubFocusAction(
4183 const IntRect& rect,
4184 blink::mojom::blink::ScrollAlignment horizontal_scroll_alignment,
4185 blink::mojom::blink::ScrollAlignment vertical_scroll_alignment) const {
4186 LayoutObject* layout_object = GetLayoutObjectForNativeScrollAction();
4187 if (!layout_object)
4188 return false;
4189
4190 PhysicalRect target_rect =
4191 layout_object->LocalToAbsoluteRect(PhysicalRect(rect));
4192 layout_object->ScrollRectToVisible(
4193 target_rect, ScrollAlignment::CreateScrollIntoViewParams(
4194 horizontal_scroll_alignment, vertical_scroll_alignment,
4195 mojom::blink::ScrollType::kProgrammatic,
4196 false /* make_visible_in_visual_viewport */,
4197 mojom::blink::ScrollBehavior::kAuto));
4198 AXObjectCache().PostNotification(
4199 AXObjectCache().GetOrCreate(GetDocument()->GetLayoutView()),
4200 ax::mojom::blink::Event::kLocationChanged);
4201 return true;
4202 }
4203
OnNativeScrollToGlobalPointAction(const IntPoint & global_point) const4204 bool AXObject::OnNativeScrollToGlobalPointAction(
4205 const IntPoint& global_point) const {
4206 LayoutObject* layout_object = GetLayoutObjectForNativeScrollAction();
4207 if (!layout_object)
4208 return false;
4209
4210 PhysicalRect target_rect(layout_object->AbsoluteBoundingBoxRect());
4211 target_rect.Move(-PhysicalOffset(global_point));
4212 layout_object->ScrollRectToVisible(
4213 target_rect,
4214 ScrollAlignment::CreateScrollIntoViewParams(
4215 ScrollAlignment::LeftAlways(), ScrollAlignment::TopAlways(),
4216 mojom::blink::ScrollType::kProgrammatic, false,
4217 mojom::blink::ScrollBehavior::kAuto));
4218 AXObjectCache().PostNotification(
4219 AXObjectCache().GetOrCreate(GetDocument()->GetLayoutView()),
4220 ax::mojom::blink::Event::kLocationChanged);
4221 return true;
4222 }
4223
OnNativeSetSequentialFocusNavigationStartingPointAction()4224 bool AXObject::OnNativeSetSequentialFocusNavigationStartingPointAction() {
4225 // Call it on the nearest ancestor that overrides this with a specific
4226 // implementation.
4227 if (ParentObject()) {
4228 return ParentObject()
4229 ->OnNativeSetSequentialFocusNavigationStartingPointAction();
4230 }
4231 return false;
4232 }
4233
OnNativeDecrementAction()4234 bool AXObject::OnNativeDecrementAction() {
4235 return false;
4236 }
4237
OnNativeFocusAction()4238 bool AXObject::OnNativeFocusAction() {
4239 return false;
4240 }
4241
OnNativeIncrementAction()4242 bool AXObject::OnNativeIncrementAction() {
4243 return false;
4244 }
4245
OnNativeSetValueAction(const String &)4246 bool AXObject::OnNativeSetValueAction(const String&) {
4247 return false;
4248 }
4249
OnNativeSetSelectedAction(bool)4250 bool AXObject::OnNativeSetSelectedAction(bool) {
4251 return false;
4252 }
4253
OnNativeShowContextMenuAction()4254 bool AXObject::OnNativeShowContextMenuAction() {
4255 Element* element = GetElement();
4256 if (!element)
4257 element = ParentObject() ? ParentObject()->GetElement() : nullptr;
4258 if (!element)
4259 return false;
4260
4261 Document* document = GetDocument();
4262 if (!document || !document->GetFrame())
4263 return false;
4264
4265 ContextMenuAllowedScope scope;
4266 document->GetFrame()->GetEventHandler().ShowNonLocatedContextMenu(
4267 element, kMenuSourceKeyboard);
4268 return true;
4269 }
4270
SelectionChanged()4271 void AXObject::SelectionChanged() {
4272 if (AXObject* parent = ParentObjectIfExists())
4273 parent->SelectionChanged();
4274 }
4275
4276 // static
IsARIAControl(ax::mojom::blink::Role aria_role)4277 bool AXObject::IsARIAControl(ax::mojom::blink::Role aria_role) {
4278 return IsARIAInput(aria_role) ||
4279 aria_role == ax::mojom::blink::Role::kButton ||
4280 aria_role == ax::mojom::blink::Role::kComboBoxMenuButton ||
4281 aria_role == ax::mojom::blink::Role::kSlider;
4282 }
4283
4284 // static
IsARIAInput(ax::mojom::blink::Role aria_role)4285 bool AXObject::IsARIAInput(ax::mojom::blink::Role aria_role) {
4286 return aria_role == ax::mojom::blink::Role::kRadioButton ||
4287 aria_role == ax::mojom::blink::Role::kCheckBox ||
4288 aria_role == ax::mojom::blink::Role::kTextField ||
4289 aria_role == ax::mojom::blink::Role::kSwitch ||
4290 aria_role == ax::mojom::blink::Role::kSearchBox ||
4291 aria_role == ax::mojom::blink::Role::kTextFieldWithComboBox;
4292 }
4293
4294 // static
HasARIAOwns(Element * element)4295 bool AXObject::HasARIAOwns(Element* element) {
4296 if (!element)
4297 return false;
4298
4299 // A LayoutObject is not required, because an invisible object can still
4300 // use aria-owns to point to visible children.
4301
4302 const AtomicString& aria_owns =
4303 element->FastGetAttribute(html_names::kAriaOwnsAttr);
4304
4305 // TODO: do we need to check !AriaOwnsElements.empty() ? Is that fundamentally
4306 // different from HasExplicitlySetAttrAssociatedElements()? And is an element
4307 // even necessary in the case of virtual nodes?
4308 return !aria_owns.IsEmpty() ||
4309 element->HasExplicitlySetAttrAssociatedElements(
4310 html_names::kAriaOwnsAttr);
4311 }
4312
AriaRoleToWebCoreRole(const String & value)4313 ax::mojom::blink::Role AXObject::AriaRoleToWebCoreRole(const String& value) {
4314 DCHECK(!value.IsEmpty());
4315
4316 static const ARIARoleMap* role_map = CreateARIARoleMap();
4317
4318 Vector<String> role_vector;
4319 value.Split(' ', role_vector);
4320 ax::mojom::blink::Role role = ax::mojom::blink::Role::kUnknown;
4321 for (const auto& child : role_vector) {
4322 role = role_map->at(child);
4323 if (role != ax::mojom::blink::Role::kUnknown)
4324 return role;
4325 }
4326
4327 return role;
4328 }
4329
NameFromSelectedOption(bool recursive) const4330 bool AXObject::NameFromSelectedOption(bool recursive) const {
4331 switch (RoleValue()) {
4332 // Step 2E from: http://www.w3.org/TR/accname-aam-1.1
4333 case ax::mojom::blink::Role::kComboBoxGrouping:
4334 case ax::mojom::blink::Role::kComboBoxMenuButton:
4335 case ax::mojom::blink::Role::kListBox:
4336 return recursive;
4337 // This can be either a button widget with a non-false value of
4338 // aria-haspopup or a select element with size of 1.
4339 case ax::mojom::blink::Role::kPopUpButton:
4340 return DynamicTo<HTMLSelectElement>(*GetNode()) ? recursive : false;
4341 default:
4342 return false;
4343 }
4344 }
4345
SupportsNameFromContents(bool recursive) const4346 bool AXObject::SupportsNameFromContents(bool recursive) const {
4347 // ARIA 1.1, section 5.2.7.5.
4348 bool result = false;
4349
4350 switch (RoleValue()) {
4351 // ----- NameFrom: contents -------------------------
4352 // Get their own name from contents, or contribute to ancestors
4353 case ax::mojom::blink::Role::kAnchor:
4354 case ax::mojom::blink::Role::kButton:
4355 case ax::mojom::blink::Role::kCell:
4356 case ax::mojom::blink::Role::kCheckBox:
4357 case ax::mojom::blink::Role::kColumnHeader:
4358 case ax::mojom::blink::Role::kDocBackLink:
4359 case ax::mojom::blink::Role::kDocBiblioRef:
4360 case ax::mojom::blink::Role::kDocNoteRef:
4361 case ax::mojom::blink::Role::kDocGlossRef:
4362 case ax::mojom::blink::Role::kDisclosureTriangle:
4363 case ax::mojom::blink::Role::kHeading:
4364 case ax::mojom::blink::Role::kLayoutTableCell:
4365 case ax::mojom::blink::Role::kLineBreak:
4366 case ax::mojom::blink::Role::kLink:
4367 case ax::mojom::blink::Role::kListBoxOption:
4368 case ax::mojom::blink::Role::kMenuItem:
4369 case ax::mojom::blink::Role::kMenuItemCheckBox:
4370 case ax::mojom::blink::Role::kMenuItemRadio:
4371 case ax::mojom::blink::Role::kMenuListOption:
4372 case ax::mojom::blink::Role::kPopUpButton:
4373 case ax::mojom::blink::Role::kPortal:
4374 case ax::mojom::blink::Role::kRadioButton:
4375 case ax::mojom::blink::Role::kRowHeader:
4376 case ax::mojom::blink::Role::kStaticText:
4377 case ax::mojom::blink::Role::kSwitch:
4378 case ax::mojom::blink::Role::kTab:
4379 case ax::mojom::blink::Role::kToggleButton:
4380 case ax::mojom::blink::Role::kTreeItem:
4381 case ax::mojom::blink::Role::kTooltip:
4382 result = true;
4383 break;
4384
4385 // ----- No name from contents -------------------------
4386 // These never have or contribute a name from contents, as they are
4387 // containers for many subobjects. Superset of nameFrom:author ARIA roles.
4388 case ax::mojom::blink::Role::kAlert:
4389 case ax::mojom::blink::Role::kAlertDialog:
4390 case ax::mojom::blink::Role::kApplication:
4391 case ax::mojom::blink::Role::kAudio:
4392 case ax::mojom::blink::Role::kArticle:
4393 case ax::mojom::blink::Role::kBanner:
4394 case ax::mojom::blink::Role::kBlockquote:
4395 case ax::mojom::blink::Role::kCaret:
4396 case ax::mojom::blink::Role::kClient:
4397 case ax::mojom::blink::Role::kColorWell:
4398 case ax::mojom::blink::Role::kColumn:
4399 case ax::mojom::blink::Role::kComboBoxMenuButton: // Only value from
4400 // content.
4401 case ax::mojom::blink::Role::kComboBoxGrouping:
4402 case ax::mojom::blink::Role::kComment:
4403 case ax::mojom::blink::Role::kComplementary:
4404 case ax::mojom::blink::Role::kContentInfo:
4405 case ax::mojom::blink::Role::kDate:
4406 case ax::mojom::blink::Role::kDateTime:
4407 case ax::mojom::blink::Role::kDesktop:
4408 case ax::mojom::blink::Role::kDialog:
4409 case ax::mojom::blink::Role::kDirectory:
4410 case ax::mojom::blink::Role::kDocCover:
4411 case ax::mojom::blink::Role::kDocBiblioEntry:
4412 case ax::mojom::blink::Role::kDocEndnote:
4413 case ax::mojom::blink::Role::kDocFootnote:
4414 case ax::mojom::blink::Role::kDocPageBreak:
4415 case ax::mojom::blink::Role::kDocPageFooter:
4416 case ax::mojom::blink::Role::kDocPageHeader:
4417 case ax::mojom::blink::Role::kDocAbstract:
4418 case ax::mojom::blink::Role::kDocAcknowledgments:
4419 case ax::mojom::blink::Role::kDocAfterword:
4420 case ax::mojom::blink::Role::kDocAppendix:
4421 case ax::mojom::blink::Role::kDocBibliography:
4422 case ax::mojom::blink::Role::kDocChapter:
4423 case ax::mojom::blink::Role::kDocColophon:
4424 case ax::mojom::blink::Role::kDocConclusion:
4425 case ax::mojom::blink::Role::kDocCredit:
4426 case ax::mojom::blink::Role::kDocCredits:
4427 case ax::mojom::blink::Role::kDocDedication:
4428 case ax::mojom::blink::Role::kDocEndnotes:
4429 case ax::mojom::blink::Role::kDocEpigraph:
4430 case ax::mojom::blink::Role::kDocEpilogue:
4431 case ax::mojom::blink::Role::kDocErrata:
4432 case ax::mojom::blink::Role::kDocExample:
4433 case ax::mojom::blink::Role::kDocForeword:
4434 case ax::mojom::blink::Role::kDocGlossary:
4435 case ax::mojom::blink::Role::kDocIndex:
4436 case ax::mojom::blink::Role::kDocIntroduction:
4437 case ax::mojom::blink::Role::kDocNotice:
4438 case ax::mojom::blink::Role::kDocPageList:
4439 case ax::mojom::blink::Role::kDocPart:
4440 case ax::mojom::blink::Role::kDocPreface:
4441 case ax::mojom::blink::Role::kDocPrologue:
4442 case ax::mojom::blink::Role::kDocPullquote:
4443 case ax::mojom::blink::Role::kDocQna:
4444 case ax::mojom::blink::Role::kDocSubtitle:
4445 case ax::mojom::blink::Role::kDocTip:
4446 case ax::mojom::blink::Role::kDocToc:
4447 case ax::mojom::blink::Role::kDocument:
4448 case ax::mojom::blink::Role::kEmbeddedObject:
4449 case ax::mojom::blink::Role::kFeed:
4450 case ax::mojom::blink::Role::kFigure:
4451 case ax::mojom::blink::Role::kForm:
4452 case ax::mojom::blink::Role::kGraphicsDocument:
4453 case ax::mojom::blink::Role::kGraphicsObject:
4454 case ax::mojom::blink::Role::kGraphicsSymbol:
4455 case ax::mojom::blink::Role::kGrid:
4456 case ax::mojom::blink::Role::kGroup:
4457 case ax::mojom::blink::Role::kHeader:
4458 case ax::mojom::blink::Role::kIframePresentational:
4459 case ax::mojom::blink::Role::kIframe:
4460 case ax::mojom::blink::Role::kImage:
4461 case ax::mojom::blink::Role::kImeCandidate: // Internal role, not used on
4462 // the web.
4463 case ax::mojom::blink::Role::kInputTime:
4464 case ax::mojom::blink::Role::kKeyboard:
4465 case ax::mojom::blink::Role::kListBox:
4466 case ax::mojom::blink::Role::kListGrid:
4467 case ax::mojom::blink::Role::kLog:
4468 case ax::mojom::blink::Role::kMain:
4469 case ax::mojom::blink::Role::kMarquee:
4470 case ax::mojom::blink::Role::kMath:
4471 case ax::mojom::blink::Role::kMenuListPopup:
4472 case ax::mojom::blink::Role::kMenu:
4473 case ax::mojom::blink::Role::kMenuBar:
4474 case ax::mojom::blink::Role::kMeter:
4475 case ax::mojom::blink::Role::kNavigation:
4476 case ax::mojom::blink::Role::kNote:
4477 case ax::mojom::blink::Role::kPane:
4478 case ax::mojom::blink::Role::kPluginObject:
4479 case ax::mojom::blink::Role::kProgressIndicator:
4480 case ax::mojom::blink::Role::kRadioGroup:
4481 case ax::mojom::blink::Role::kRowGroup:
4482 case ax::mojom::blink::Role::kScrollBar:
4483 case ax::mojom::blink::Role::kScrollView:
4484 case ax::mojom::blink::Role::kSearch:
4485 case ax::mojom::blink::Role::kSearchBox:
4486 case ax::mojom::blink::Role::kSplitter:
4487 case ax::mojom::blink::Role::kSlider:
4488 case ax::mojom::blink::Role::kSpinButton:
4489 case ax::mojom::blink::Role::kStatus:
4490 case ax::mojom::blink::Role::kSliderThumb:
4491 case ax::mojom::blink::Role::kSuggestion:
4492 case ax::mojom::blink::Role::kSvgRoot:
4493 case ax::mojom::blink::Role::kTable:
4494 case ax::mojom::blink::Role::kTableHeaderContainer:
4495 case ax::mojom::blink::Role::kTabList:
4496 case ax::mojom::blink::Role::kTabPanel:
4497 case ax::mojom::blink::Role::kTerm:
4498 case ax::mojom::blink::Role::kTextField:
4499 case ax::mojom::blink::Role::kTextFieldWithComboBox:
4500 case ax::mojom::blink::Role::kTitleBar:
4501 case ax::mojom::blink::Role::kTimer:
4502 case ax::mojom::blink::Role::kToolbar:
4503 case ax::mojom::blink::Role::kTree:
4504 case ax::mojom::blink::Role::kTreeGrid:
4505 case ax::mojom::blink::Role::kVideo:
4506 case ax::mojom::blink::Role::kWebArea:
4507 case ax::mojom::blink::Role::kWebView:
4508 case ax::mojom::blink::Role::kWindow:
4509 result = false;
4510 break;
4511
4512 // ----- Conditional: contribute to ancestor only, unless focusable -------
4513 // Some objects can contribute their contents to ancestor names, but
4514 // only have their own name if they are focusable
4515 case ax::mojom::blink::Role::kAbbr:
4516 case ax::mojom::blink::Role::kCanvas:
4517 case ax::mojom::blink::Role::kCaption:
4518 case ax::mojom::blink::Role::kCode:
4519 case ax::mojom::blink::Role::kContentDeletion:
4520 case ax::mojom::blink::Role::kContentInsertion:
4521 case ax::mojom::blink::Role::kDefinition:
4522 case ax::mojom::blink::Role::kDescriptionListDetail:
4523 case ax::mojom::blink::Role::kDescriptionList:
4524 case ax::mojom::blink::Role::kDescriptionListTerm:
4525 case ax::mojom::blink::Role::kDetails:
4526 case ax::mojom::blink::Role::kEmphasis:
4527 case ax::mojom::blink::Role::kFigcaption:
4528 case ax::mojom::blink::Role::kFooter:
4529 case ax::mojom::blink::Role::kFooterAsNonLandmark:
4530 case ax::mojom::blink::Role::kGenericContainer:
4531 case ax::mojom::blink::Role::kHeaderAsNonLandmark:
4532 case ax::mojom::blink::Role::kIgnored:
4533 case ax::mojom::blink::Role::kImageMap:
4534 case ax::mojom::blink::Role::kInlineTextBox:
4535 case ax::mojom::blink::Role::kLabelText:
4536 case ax::mojom::blink::Role::kLayoutTable:
4537 case ax::mojom::blink::Role::kLayoutTableRow:
4538 case ax::mojom::blink::Role::kLegend:
4539 case ax::mojom::blink::Role::kList:
4540 case ax::mojom::blink::Role::kListItem:
4541 case ax::mojom::blink::Role::kListMarker:
4542 case ax::mojom::blink::Role::kMark:
4543 case ax::mojom::blink::Role::kNone:
4544 case ax::mojom::blink::Role::kParagraph:
4545 case ax::mojom::blink::Role::kPre:
4546 case ax::mojom::blink::Role::kPresentational:
4547 case ax::mojom::blink::Role::kRegion:
4548 // Spec says we should always expose the name on rows,
4549 // but for performance reasons we only do it
4550 // if the row might receive focus
4551 case ax::mojom::blink::Role::kRow:
4552 case ax::mojom::blink::Role::kRuby:
4553 case ax::mojom::blink::Role::kRubyAnnotation:
4554 case ax::mojom::blink::Role::kSection:
4555 case ax::mojom::blink::Role::kStrong:
4556 case ax::mojom::blink::Role::kTime:
4557 if (recursive) {
4558 // Use contents if part of a recursive name computation.
4559 result = true;
4560 } else {
4561 // Use contents if focusable, so that there is a name in the case
4562 // where the author mistakenly forgot to provide one.
4563 // Exceptions:
4564 // 1.Elements with contenteditable, where using the contents as a name
4565 // would cause them to be double-announced.
4566 // 2.Containers with aria-activedescendant, where the focus is being
4567 // forwarded somewhere else.
4568 // TODO(accessibility) Scrollables are currently allowed here in order
4569 // to keep the current behavior. In the future, this can be removed
4570 // because this code will be handled in IsFocusable(), once
4571 // KeyboardFocusableScrollersEnabled is permanently enabled.
4572 // Note: this uses the same scrollable check that element.cc uses.
4573 bool is_focusable_scrollable =
4574 RuntimeEnabledFeatures::KeyboardFocusableScrollersEnabled() &&
4575 IsUserScrollable();
4576 bool is_focusable = is_focusable_scrollable || CanSetFocusAttribute();
4577 result = is_focusable && !IsEditable() &&
4578 !GetAOMPropertyOrARIAAttribute(
4579 AOMRelationProperty::kActiveDescendant);
4580 }
4581 break;
4582
4583 case ax::mojom::blink::Role::kPdfActionableHighlight:
4584 LOG(ERROR) << "PDF specific highlight role, Blink shouldn't generate "
4585 "this role type";
4586 NOTREACHED();
4587 break;
4588
4589 // A root web area normally only computes its name from the document title,
4590 // but a root web area inside a portal's main frame should compute its name
4591 // from its contents. This name is used by the portal element that hosts
4592 // this portal.
4593 case ax::mojom::blink::Role::kRootWebArea: {
4594 DCHECK(GetNode());
4595 const Document& document = GetNode()->GetDocument();
4596 bool is_main_frame =
4597 document.GetFrame() && document.GetFrame()->IsMainFrame();
4598 bool is_inside_portal =
4599 document.GetPage() && document.GetPage()->InsidePortal();
4600 return is_inside_portal && is_main_frame;
4601 }
4602
4603 case ax::mojom::blink::Role::kUnknown:
4604 LOG(ERROR) << "ax::mojom::blink::Role::kUnknown for " << GetNode();
4605 NOTREACHED();
4606 break;
4607 }
4608
4609 return result;
4610 }
4611
SupportsARIAReadOnly() const4612 bool AXObject::SupportsARIAReadOnly() const {
4613 if (ui::IsReadOnlySupported(RoleValue()))
4614 return true;
4615
4616 if (ui::IsCellOrTableHeader(RoleValue())) {
4617 // For cells and row/column headers, readonly is supported within a grid.
4618 return std::any_of(
4619 UnignoredAncestorsBegin(), UnignoredAncestorsEnd(),
4620 [](const AXObject& ancestor) {
4621 return ancestor.RoleValue() == ax::mojom::blink::Role::kGrid ||
4622 ancestor.RoleValue() == ax::mojom::blink::Role::kTreeGrid;
4623 });
4624 }
4625
4626 return false;
4627 }
4628
ButtonRoleType() const4629 ax::mojom::blink::Role AXObject::ButtonRoleType() const {
4630 // If aria-pressed is present, then it should be exposed as a toggle button.
4631 // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed
4632 if (AriaPressedIsPresent())
4633 return ax::mojom::blink::Role::kToggleButton;
4634 if (HasPopup() != ax::mojom::blink::HasPopup::kFalse)
4635 return ax::mojom::blink::Role::kPopUpButton;
4636 // We don't contemplate RadioButtonRole, as it depends on the input
4637 // type.
4638
4639 return ax::mojom::blink::Role::kButton;
4640 }
4641
4642 // static
RoleName(ax::mojom::blink::Role role)4643 const AtomicString& AXObject::RoleName(ax::mojom::blink::Role role) {
4644 static const Vector<AtomicString>* role_name_vector = CreateRoleNameVector();
4645
4646 return role_name_vector->at(static_cast<wtf_size_t>(role));
4647 }
4648
4649 // static
InternalRoleName(ax::mojom::blink::Role role)4650 const AtomicString& AXObject::InternalRoleName(ax::mojom::blink::Role role) {
4651 static const Vector<AtomicString>* internal_role_name_vector =
4652 CreateInternalRoleNameVector();
4653
4654 return internal_role_name_vector->at(static_cast<wtf_size_t>(role));
4655 }
4656
4657 // static
LowestCommonAncestor(const AXObject & first,const AXObject & second,int * index_in_ancestor1,int * index_in_ancestor2)4658 const AXObject* AXObject::LowestCommonAncestor(const AXObject& first,
4659 const AXObject& second,
4660 int* index_in_ancestor1,
4661 int* index_in_ancestor2) {
4662 *index_in_ancestor1 = -1;
4663 *index_in_ancestor2 = -1;
4664
4665 if (first.IsDetached() || second.IsDetached())
4666 return nullptr;
4667
4668 if (first == second)
4669 return &first;
4670
4671 HeapVector<Member<const AXObject>> ancestors1;
4672 ancestors1.push_back(&first);
4673 while (ancestors1.back())
4674 ancestors1.push_back(ancestors1.back()->ParentObjectIncludedInTree());
4675
4676 HeapVector<Member<const AXObject>> ancestors2;
4677 ancestors2.push_back(&second);
4678 while (ancestors2.back())
4679 ancestors2.push_back(ancestors2.back()->ParentObjectIncludedInTree());
4680
4681 const AXObject* common_ancestor = nullptr;
4682 while (!ancestors1.IsEmpty() && !ancestors2.IsEmpty() &&
4683 ancestors1.back() == ancestors2.back()) {
4684 common_ancestor = ancestors1.back();
4685 ancestors1.pop_back();
4686 ancestors2.pop_back();
4687 }
4688
4689 if (common_ancestor) {
4690 if (!ancestors1.IsEmpty())
4691 *index_in_ancestor1 = ancestors1.back()->IndexInParent();
4692 if (!ancestors2.IsEmpty())
4693 *index_in_ancestor2 = ancestors2.back()->IndexInParent();
4694 }
4695
4696 return common_ancestor;
4697 }
4698
ToString(bool verbose) const4699 String AXObject::ToString(bool verbose) const {
4700 // Build a friendly name for debugging the object.
4701 // If verbose, build a longer name name in the form of:
4702 // CheckBox axid#28 <input.someClass#cbox1> name="checkbox"
4703 String string_builder =
4704 AXObject::InternalRoleName(RoleValue()).GetString().EncodeForDebugging();
4705
4706 if (verbose) {
4707 string_builder = string_builder + " axid#" + String::Number(AXObjectID());
4708 // Add useful HTML element info, like <div.myClass#myId>.
4709 if (GetElement()) {
4710 string_builder =
4711 string_builder + " <" + GetElement()->tagName().LowerASCII();
4712 if (GetElement()->FastHasAttribute(html_names::kClassAttr)) {
4713 string_builder = string_builder + "." +
4714 GetElement()->FastGetAttribute(html_names::kClassAttr);
4715 }
4716 if (GetElement()->FastHasAttribute(html_names::kIdAttr)) {
4717 string_builder = string_builder + "#" +
4718 GetElement()->FastGetAttribute(html_names::kIdAttr);
4719 }
4720 string_builder = string_builder + ">";
4721 }
4722
4723 // Add properties of interest that often contribute to errors:
4724 if (HasARIAOwns(GetElement()))
4725 string_builder = string_builder + " @aria-owns";
4726 if (GetAOMPropertyOrARIAAttribute(AOMRelationProperty::kActiveDescendant))
4727 string_builder = string_builder + " @aria-activedescendant";
4728 if (IsFocused())
4729 string_builder = string_builder + " focused";
4730 if (AXObjectCache().IsAriaOwned(this))
4731 string_builder = string_builder + " isAriaOwned";
4732 if (AccessibilityIsIgnored()) {
4733 string_builder = string_builder + " isIgnored";
4734 if (!AccessibilityIsIncludedInTree())
4735 string_builder = string_builder + " isRemovedFromTree";
4736 }
4737 if (GetNode() &&
4738 DisplayLockUtilities::ShouldIgnoreNodeDueToDisplayLock(
4739 *GetNode(), DisplayLockActivationReason::kAccessibility)) {
4740 string_builder = string_builder + " isDisplayLocked";
4741 }
4742 if (AriaHiddenRoot())
4743 string_builder = string_builder + " isAriaHidden";
4744 if (IsHiddenViaStyle())
4745 string_builder = string_builder + " isHiddenViaCSS";
4746 if (GetNode() && GetNode()->IsInert())
4747 string_builder = string_builder + " isInert";
4748
4749 string_builder = string_builder + " name=";
4750 } else {
4751 string_builder = string_builder + ": ";
4752 }
4753
4754 // Append name last, in case it is long.
4755 return string_builder + ComputedName().EncodeForDebugging();
4756 }
4757
operator ==(const AXObject & first,const AXObject & second)4758 bool operator==(const AXObject& first, const AXObject& second) {
4759 if (first.IsDetached() || second.IsDetached())
4760 return false;
4761 if (&first == &second) {
4762 DCHECK_EQ(first.AXObjectID(), second.AXObjectID());
4763 return true;
4764 }
4765 return false;
4766 }
4767
operator !=(const AXObject & first,const AXObject & second)4768 bool operator!=(const AXObject& first, const AXObject& second) {
4769 return !(first == second);
4770 }
4771
operator <(const AXObject & first,const AXObject & second)4772 bool operator<(const AXObject& first, const AXObject& second) {
4773 if (first.IsDetached() || second.IsDetached())
4774 return false;
4775
4776 int index_in_ancestor1, index_in_ancestor2;
4777 const AXObject* ancestor = AXObject::LowestCommonAncestor(
4778 first, second, &index_in_ancestor1, &index_in_ancestor2);
4779 DCHECK_GE(index_in_ancestor1, -1);
4780 DCHECK_GE(index_in_ancestor2, -1);
4781 if (!ancestor)
4782 return false;
4783 return index_in_ancestor1 < index_in_ancestor2;
4784 }
4785
operator <=(const AXObject & first,const AXObject & second)4786 bool operator<=(const AXObject& first, const AXObject& second) {
4787 return first == second || first < second;
4788 }
4789
operator >(const AXObject & first,const AXObject & second)4790 bool operator>(const AXObject& first, const AXObject& second) {
4791 if (first.IsDetached() || second.IsDetached())
4792 return false;
4793
4794 int index_in_ancestor1, index_in_ancestor2;
4795 const AXObject* ancestor = AXObject::LowestCommonAncestor(
4796 first, second, &index_in_ancestor1, &index_in_ancestor2);
4797 DCHECK_GE(index_in_ancestor1, -1);
4798 DCHECK_GE(index_in_ancestor2, -1);
4799 if (!ancestor)
4800 return false;
4801 return index_in_ancestor1 > index_in_ancestor2;
4802 }
4803
operator >=(const AXObject & first,const AXObject & second)4804 bool operator>=(const AXObject& first, const AXObject& second) {
4805 return first == second || first > second;
4806 }
4807
operator <<(std::ostream & stream,const AXObject & obj)4808 std::ostream& operator<<(std::ostream& stream, const AXObject& obj) {
4809 return stream << obj.ToString(true).Utf8();
4810 }
4811
Trace(Visitor * visitor) const4812 void AXObject::Trace(Visitor* visitor) const {
4813 visitor->Trace(children_);
4814 visitor->Trace(parent_);
4815 visitor->Trace(cached_live_region_root_);
4816 visitor->Trace(ax_object_cache_);
4817 }
4818
4819 } // namespace blink
4820