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 "third_party/blink/public/common/features.h"
32 #include "third_party/blink/public/mojom/input/focus_type.mojom-blink.h"
33 #include "third_party/blink/renderer/core/aom/accessible_node.h"
34 #include "third_party/blink/renderer/core/aom/accessible_node_list.h"
35 #include "third_party/blink/renderer/core/css/resolver/style_resolver.h"
36 #include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
37 #include "third_party/blink/renderer/core/dom/dom_node_ids.h"
38 #include "third_party/blink/renderer/core/dom/element_traversal.h"
39 #include "third_party/blink/renderer/core/frame/local_frame.h"
40 #include "third_party/blink/renderer/core/frame/local_frame_view.h"
41 #include "third_party/blink/renderer/core/frame/settings.h"
42 #include "third_party/blink/renderer/core/html/canvas/html_canvas_element.h"
43 #include "third_party/blink/renderer/core/html/custom/element_internals.h"
44 #include "third_party/blink/renderer/core/html/forms/html_input_element.h"
45 #include "third_party/blink/renderer/core/html/forms/html_select_element.h"
46 #include "third_party/blink/renderer/core/html/html_dialog_element.h"
47 #include "third_party/blink/renderer/core/html/html_frame_owner_element.h"
48 #include "third_party/blink/renderer/core/html/parser/html_parser_idioms.h"
49 #include "third_party/blink/renderer/core/input/context_menu_allowed_scope.h"
50 #include "third_party/blink/renderer/core/input/event_handler.h"
51 #include "third_party/blink/renderer/core/input_type_names.h"
52 #include "third_party/blink/renderer/core/layout/layout_box.h"
53 #include "third_party/blink/renderer/core/layout/layout_box_model_object.h"
54 #include "third_party/blink/renderer/core/layout/layout_view.h"
55 #include "third_party/blink/renderer/core/page/chrome_client.h"
56 #include "third_party/blink/renderer/core/page/focus_controller.h"
57 #include "third_party/blink/renderer/core/page/page.h"
58 #include "third_party/blink/renderer/core/page/scrolling/top_document_root_scroller_controller.h"
59 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list.h"
60 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list_option.h"
61 #include "third_party/blink/renderer/modules/accessibility/ax_menu_list_popup.h"
62 #include "third_party/blink/renderer/modules/accessibility/ax_object_cache_impl.h"
63 #include "third_party/blink/renderer/modules/accessibility/ax_range.h"
64 #include "third_party/blink/renderer/modules/accessibility/ax_sparse_attribute_setter.h"
65 #include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
66 #include "third_party/blink/renderer/platform/language.h"
67 #include "third_party/blink/renderer/platform/runtime_enabled_features.h"
68 #include "third_party/blink/renderer/platform/text/platform_locale.h"
69 #include "third_party/blink/renderer/platform/wtf/hash_set.h"
70 #include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
71 #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
72 #include "third_party/blink/renderer/platform/wtf/wtf_size_t.h"
73 #include "third_party/skia/include/core/SkMatrix44.h"
74
75 namespace blink {
76
77 namespace {
78
79 struct RoleHashTraits : HashTraits<ax::mojom::Role> {
80 static const bool kEmptyValueIsZero = true;
EmptyValueblink::__anon87d600600111::RoleHashTraits81 static ax::mojom::Role EmptyValue() { return ax::mojom::Role::kUnknown; }
82 };
83
84 using ARIARoleMap = HashMap<String,
85 ax::mojom::Role,
86 CaseFoldingHash,
87 HashTraits<String>,
88 RoleHashTraits>;
89
90 struct RoleEntry {
91 const char* aria_role;
92 ax::mojom::Role webcore_role;
93 };
94
95 // Mapping of ARIA role name to internal role name.
96 const RoleEntry kRoles[] = {
97 {"alert", ax::mojom::Role::kAlert},
98 {"alertdialog", ax::mojom::Role::kAlertDialog},
99 {"application", ax::mojom::Role::kApplication},
100 {"article", ax::mojom::Role::kArticle},
101 {"banner", ax::mojom::Role::kBanner},
102 {"blockquote", ax::mojom::Role::kBlockquote},
103 {"button", ax::mojom::Role::kButton},
104 {"caption", ax::mojom::Role::kCaption},
105 {"cell", ax::mojom::Role::kCell},
106 {"code", ax::mojom::Role::kCode},
107 {"checkbox", ax::mojom::Role::kCheckBox},
108 {"columnheader", ax::mojom::Role::kColumnHeader},
109 {"combobox", ax::mojom::Role::kComboBoxGrouping},
110 {"comment", ax::mojom::Role::kComment},
111 {"complementary", ax::mojom::Role::kComplementary},
112 {"contentinfo", ax::mojom::Role::kContentInfo},
113 {"definition", ax::mojom::Role::kDefinition},
114 {"deletion", ax::mojom::Role::kContentDeletion},
115 {"dialog", ax::mojom::Role::kDialog},
116 {"directory", ax::mojom::Role::kDirectory},
117 // -------------------------------------------------
118 // DPub Roles:
119 // www.w3.org/TR/dpub-aam-1.0/#mapping_role_table
120 {"doc-abstract", ax::mojom::Role::kDocAbstract},
121 {"doc-acknowledgments", ax::mojom::Role::kDocAcknowledgments},
122 {"doc-afterword", ax::mojom::Role::kDocAfterword},
123 {"doc-appendix", ax::mojom::Role::kDocAppendix},
124 {"doc-backlink", ax::mojom::Role::kDocBackLink},
125 {"doc-biblioentry", ax::mojom::Role::kDocBiblioEntry},
126 {"doc-bibliography", ax::mojom::Role::kDocBibliography},
127 {"doc-biblioref", ax::mojom::Role::kDocBiblioRef},
128 {"doc-chapter", ax::mojom::Role::kDocChapter},
129 {"doc-colophon", ax::mojom::Role::kDocColophon},
130 {"doc-conclusion", ax::mojom::Role::kDocConclusion},
131 {"doc-cover", ax::mojom::Role::kDocCover},
132 {"doc-credit", ax::mojom::Role::kDocCredit},
133 {"doc-credits", ax::mojom::Role::kDocCredits},
134 {"doc-dedication", ax::mojom::Role::kDocDedication},
135 {"doc-endnote", ax::mojom::Role::kDocEndnote},
136 {"doc-endnotes", ax::mojom::Role::kDocEndnotes},
137 {"doc-epigraph", ax::mojom::Role::kDocEpigraph},
138 {"doc-epilogue", ax::mojom::Role::kDocEpilogue},
139 {"doc-errata", ax::mojom::Role::kDocErrata},
140 {"doc-example", ax::mojom::Role::kDocExample},
141 {"doc-footnote", ax::mojom::Role::kDocFootnote},
142 {"doc-foreword", ax::mojom::Role::kDocForeword},
143 {"doc-glossary", ax::mojom::Role::kDocGlossary},
144 {"doc-glossref", ax::mojom::Role::kDocGlossRef},
145 {"doc-index", ax::mojom::Role::kDocIndex},
146 {"doc-introduction", ax::mojom::Role::kDocIntroduction},
147 {"doc-noteref", ax::mojom::Role::kDocNoteRef},
148 {"doc-notice", ax::mojom::Role::kDocNotice},
149 {"doc-pagebreak", ax::mojom::Role::kDocPageBreak},
150 {"doc-pagelist", ax::mojom::Role::kDocPageList},
151 {"doc-part", ax::mojom::Role::kDocPart},
152 {"doc-preface", ax::mojom::Role::kDocPreface},
153 {"doc-prologue", ax::mojom::Role::kDocPrologue},
154 {"doc-pullquote", ax::mojom::Role::kDocPullquote},
155 {"doc-qna", ax::mojom::Role::kDocQna},
156 {"doc-subtitle", ax::mojom::Role::kDocSubtitle},
157 {"doc-tip", ax::mojom::Role::kDocTip},
158 {"doc-toc", ax::mojom::Role::kDocToc},
159 // End DPub roles.
160 // -------------------------------------------------
161 {"document", ax::mojom::Role::kDocument},
162 {"emphasis", ax::mojom::Role::kEmphasis},
163 {"feed", ax::mojom::Role::kFeed},
164 {"figure", ax::mojom::Role::kFigure},
165 {"form", ax::mojom::Role::kForm},
166 {"generic", ax::mojom::Role::kGenericContainer},
167 // -------------------------------------------------
168 // ARIA Graphics module roles:
169 // https://rawgit.com/w3c/graphics-aam/master/
170 {"graphics-document", ax::mojom::Role::kGraphicsDocument},
171 {"graphics-object", ax::mojom::Role::kGraphicsObject},
172 {"graphics-symbol", ax::mojom::Role::kGraphicsSymbol},
173 // End ARIA Graphics module roles.
174 // -------------------------------------------------
175 {"grid", ax::mojom::Role::kGrid},
176 {"gridcell", ax::mojom::Role::kCell},
177 {"group", ax::mojom::Role::kGroup},
178 {"heading", ax::mojom::Role::kHeading},
179 {"img", ax::mojom::Role::kImage},
180 {"insertion", ax::mojom::Role::kContentInsertion},
181 {"link", ax::mojom::Role::kLink},
182 {"list", ax::mojom::Role::kList},
183 {"listbox", ax::mojom::Role::kListBox},
184 {"listitem", ax::mojom::Role::kListItem},
185 {"log", ax::mojom::Role::kLog},
186 {"main", ax::mojom::Role::kMain},
187 {"marquee", ax::mojom::Role::kMarquee},
188 {"math", ax::mojom::Role::kMath},
189 {"menu", ax::mojom::Role::kMenu},
190 {"menubar", ax::mojom::Role::kMenuBar},
191 {"menuitem", ax::mojom::Role::kMenuItem},
192 {"menuitemcheckbox", ax::mojom::Role::kMenuItemCheckBox},
193 {"menuitemradio", ax::mojom::Role::kMenuItemRadio},
194 {"mark", ax::mojom::Role::kMark},
195 {"meter", ax::mojom::Role::kMeter},
196 {"navigation", ax::mojom::Role::kNavigation},
197 {"none", ax::mojom::Role::kNone},
198 {"note", ax::mojom::Role::kNote},
199 {"option", ax::mojom::Role::kListBoxOption},
200 {"paragraph", ax::mojom::Role::kParagraph},
201 {"presentation", ax::mojom::Role::kPresentational},
202 {"progressbar", ax::mojom::Role::kProgressIndicator},
203 {"radio", ax::mojom::Role::kRadioButton},
204 {"radiogroup", ax::mojom::Role::kRadioGroup},
205 // TODO(accessibility) region should only be mapped
206 // if name present. See http://crbug.com/840819.
207 {"region", ax::mojom::Role::kRegion},
208 {"row", ax::mojom::Role::kRow},
209 {"rowgroup", ax::mojom::Role::kRowGroup},
210 {"rowheader", ax::mojom::Role::kRowHeader},
211 {"scrollbar", ax::mojom::Role::kScrollBar},
212 {"search", ax::mojom::Role::kSearch},
213 {"searchbox", ax::mojom::Role::kSearchBox},
214 {"separator", ax::mojom::Role::kSplitter},
215 {"slider", ax::mojom::Role::kSlider},
216 {"spinbutton", ax::mojom::Role::kSpinButton},
217 {"status", ax::mojom::Role::kStatus},
218 {"strong", ax::mojom::Role::kStrong},
219 {"suggestion", ax::mojom::Role::kSuggestion},
220 {"switch", ax::mojom::Role::kSwitch},
221 {"tab", ax::mojom::Role::kTab},
222 {"table", ax::mojom::Role::kTable},
223 {"tablist", ax::mojom::Role::kTabList},
224 {"tabpanel", ax::mojom::Role::kTabPanel},
225 {"term", ax::mojom::Role::kTerm},
226 {"text", ax::mojom::Role::kStaticText},
227 {"textbox", ax::mojom::Role::kTextField},
228 {"time", ax::mojom::Role::kTime},
229 {"timer", ax::mojom::Role::kTimer},
230 {"toolbar", ax::mojom::Role::kToolbar},
231 {"tooltip", ax::mojom::Role::kTooltip},
232 {"tree", ax::mojom::Role::kTree},
233 {"treegrid", ax::mojom::Role::kTreeGrid},
234 {"treeitem", ax::mojom::Role::kTreeItem}};
235
236 struct InternalRoleEntry {
237 ax::mojom::Role webcore_role;
238 const char* internal_role_name;
239 };
240
241 const InternalRoleEntry kInternalRoles[] = {
242 {ax::mojom::Role::kNone, "None"},
243 {ax::mojom::Role::kAbbr, "Abbr"},
244 {ax::mojom::Role::kAlertDialog, "AlertDialog"},
245 {ax::mojom::Role::kAlert, "Alert"},
246 {ax::mojom::Role::kAnchor, "Anchor"},
247 {ax::mojom::Role::kComment, "Comment"},
248 {ax::mojom::Role::kApplication, "Application"},
249 {ax::mojom::Role::kArticle, "Article"},
250 {ax::mojom::Role::kAudio, "Audio"},
251 {ax::mojom::Role::kBanner, "Banner"},
252 {ax::mojom::Role::kBlockquote, "Blockquote"},
253 {ax::mojom::Role::kButton, "Button"},
254 {ax::mojom::Role::kCanvas, "Canvas"},
255 {ax::mojom::Role::kCaption, "Caption"},
256 {ax::mojom::Role::kCaret, "Caret"},
257 {ax::mojom::Role::kCell, "Cell"},
258 {ax::mojom::Role::kCheckBox, "CheckBox"},
259 {ax::mojom::Role::kClient, "Client"},
260 {ax::mojom::Role::kCode, "Code"},
261 {ax::mojom::Role::kColorWell, "ColorWell"},
262 {ax::mojom::Role::kColumnHeader, "ColumnHeader"},
263 {ax::mojom::Role::kColumn, "Column"},
264 {ax::mojom::Role::kComboBoxGrouping, "ComboBox"},
265 {ax::mojom::Role::kComboBoxMenuButton, "ComboBox"},
266 {ax::mojom::Role::kComplementary, "Complementary"},
267 {ax::mojom::Role::kContentDeletion, "ContentDeletion"},
268 {ax::mojom::Role::kContentInsertion, "ContentInsertion"},
269 {ax::mojom::Role::kContentInfo, "ContentInfo"},
270 {ax::mojom::Role::kDate, "Date"},
271 {ax::mojom::Role::kDateTime, "DateTime"},
272 {ax::mojom::Role::kDefinition, "Definition"},
273 {ax::mojom::Role::kDescriptionListDetail, "DescriptionListDetail"},
274 {ax::mojom::Role::kDescriptionList, "DescriptionList"},
275 {ax::mojom::Role::kDescriptionListTerm, "DescriptionListTerm"},
276 {ax::mojom::Role::kDesktop, "Desktop"},
277 {ax::mojom::Role::kDetails, "Details"},
278 {ax::mojom::Role::kDialog, "Dialog"},
279 {ax::mojom::Role::kDirectory, "Directory"},
280 {ax::mojom::Role::kDisclosureTriangle, "DisclosureTriangle"},
281 // --------------------------------------------------------------
282 // DPub Roles:
283 // https://www.w3.org/TR/dpub-aam-1.0/#mapping_role_table
284 {ax::mojom::Role::kDocAbstract, "DocAbstract"},
285 {ax::mojom::Role::kDocAcknowledgments, "DocAcknowledgments"},
286 {ax::mojom::Role::kDocAfterword, "DocAfterword"},
287 {ax::mojom::Role::kDocAppendix, "DocAppendix"},
288 {ax::mojom::Role::kDocBackLink, "DocBackLink"},
289 {ax::mojom::Role::kDocBiblioEntry, "DocBiblioentry"},
290 {ax::mojom::Role::kDocBibliography, "DocBibliography"},
291 {ax::mojom::Role::kDocBiblioRef, "DocBiblioref"},
292 {ax::mojom::Role::kDocChapter, "DocChapter"},
293 {ax::mojom::Role::kDocColophon, "DocColophon"},
294 {ax::mojom::Role::kDocConclusion, "DocConclusion"},
295 {ax::mojom::Role::kDocCover, "DocCover"},
296 {ax::mojom::Role::kDocCredit, "DocCredit"},
297 {ax::mojom::Role::kDocCredits, "DocCredits"},
298 {ax::mojom::Role::kDocDedication, "DocDedication"},
299 {ax::mojom::Role::kDocEndnote, "DocEndnote"},
300 {ax::mojom::Role::kDocEndnotes, "DocEndnotes"},
301 {ax::mojom::Role::kDocEpigraph, "DocEpigraph"},
302 {ax::mojom::Role::kDocEpilogue, "DocEpilogue"},
303 {ax::mojom::Role::kDocErrata, "DocErrata"},
304 {ax::mojom::Role::kDocExample, "DocExample"},
305 {ax::mojom::Role::kDocFootnote, "DocFootnote"},
306 {ax::mojom::Role::kDocForeword, "DocForeword"},
307 {ax::mojom::Role::kDocGlossary, "DocGlossary"},
308 {ax::mojom::Role::kDocGlossRef, "DocGlossref"},
309 {ax::mojom::Role::kDocIndex, "DocIndex"},
310 {ax::mojom::Role::kDocIntroduction, "DocIntroduction"},
311 {ax::mojom::Role::kDocNoteRef, "DocNoteref"},
312 {ax::mojom::Role::kDocNotice, "DocNotice"},
313 {ax::mojom::Role::kDocPageBreak, "DocPagebreak"},
314 {ax::mojom::Role::kDocPageList, "DocPagelist"},
315 {ax::mojom::Role::kDocPart, "DocPart"},
316 {ax::mojom::Role::kDocPreface, "DocPreface"},
317 {ax::mojom::Role::kDocPrologue, "DocPrologue"},
318 {ax::mojom::Role::kDocPullquote, "DocPullquote"},
319 {ax::mojom::Role::kDocQna, "DocQna"},
320 {ax::mojom::Role::kDocSubtitle, "DocSubtitle"},
321 {ax::mojom::Role::kDocTip, "DocTip"},
322 {ax::mojom::Role::kDocToc, "DocToc"},
323 // End DPub roles.
324 // --------------------------------------------------------------
325 {ax::mojom::Role::kDocument, "Document"},
326 {ax::mojom::Role::kEmbeddedObject, "EmbeddedObject"},
327 {ax::mojom::Role::kEmphasis, "Emphasis"},
328 {ax::mojom::Role::kFeed, "feed"},
329 {ax::mojom::Role::kFigcaption, "Figcaption"},
330 {ax::mojom::Role::kFigure, "Figure"},
331 {ax::mojom::Role::kFooter, "Footer"},
332 {ax::mojom::Role::kFooterAsNonLandmark, "FooterAsNonLandmark"},
333 {ax::mojom::Role::kForm, "Form"},
334 {ax::mojom::Role::kGenericContainer, "GenericContainer"},
335 // --------------------------------------------------------------
336 // ARIA Graphics module roles:
337 // https://rawgit.com/w3c/graphics-aam/master/#mapping_role_table
338 {ax::mojom::Role::kGraphicsDocument, "GraphicsDocument"},
339 {ax::mojom::Role::kGraphicsObject, "GraphicsObject"},
340 {ax::mojom::Role::kGraphicsSymbol, "GraphicsSymbol"},
341 // End ARIA Graphics module roles.
342 // --------------------------------------------------------------
343 {ax::mojom::Role::kGrid, "Grid"},
344 {ax::mojom::Role::kGroup, "Group"},
345 {ax::mojom::Role::kHeader, "Header"},
346 {ax::mojom::Role::kHeaderAsNonLandmark, "HeaderAsNonLandmark"},
347 {ax::mojom::Role::kHeading, "Heading"},
348 {ax::mojom::Role::kIframePresentational, "IframePresentational"},
349 {ax::mojom::Role::kIframe, "Iframe"},
350 {ax::mojom::Role::kIgnored, "Ignored"},
351 {ax::mojom::Role::kImageMap, "ImageMap"},
352 {ax::mojom::Role::kImage, "Image"},
353 {ax::mojom::Role::kInlineTextBox, "InlineTextBox"},
354 {ax::mojom::Role::kInputTime, "InputTime"},
355 {ax::mojom::Role::kKeyboard, "Keyboard"},
356 {ax::mojom::Role::kLabelText, "Label"},
357 {ax::mojom::Role::kLayoutTable, "LayoutTable"},
358 {ax::mojom::Role::kLayoutTableCell, "LayoutCellTable"},
359 {ax::mojom::Role::kLayoutTableRow, "LayoutRowTable"},
360 {ax::mojom::Role::kLegend, "Legend"},
361 {ax::mojom::Role::kLink, "Link"},
362 {ax::mojom::Role::kLineBreak, "LineBreak"},
363 {ax::mojom::Role::kListBox, "ListBox"},
364 {ax::mojom::Role::kListBoxOption, "ListBoxOption"},
365 {ax::mojom::Role::kListGrid, "ListGrid"},
366 {ax::mojom::Role::kListItem, "ListItem"},
367 {ax::mojom::Role::kListMarker, "ListMarker"},
368 {ax::mojom::Role::kList, "List"},
369 {ax::mojom::Role::kLog, "Log"},
370 {ax::mojom::Role::kMain, "Main"},
371 {ax::mojom::Role::kMark, "Mark"},
372 {ax::mojom::Role::kMarquee, "Marquee"},
373 {ax::mojom::Role::kMath, "Math"},
374 {ax::mojom::Role::kMenuBar, "MenuBar"},
375 {ax::mojom::Role::kMenuButton, "MenuButton"},
376 {ax::mojom::Role::kMenuItem, "MenuItem"},
377 {ax::mojom::Role::kMenuItemCheckBox, "MenuItemCheckBox"},
378 {ax::mojom::Role::kMenuItemRadio, "MenuItemRadio"},
379 {ax::mojom::Role::kMenuListOption, "MenuListOption"},
380 {ax::mojom::Role::kMenuListPopup, "MenuListPopup"},
381 {ax::mojom::Role::kMenu, "Menu"},
382 {ax::mojom::Role::kMeter, "Meter"},
383 {ax::mojom::Role::kNavigation, "Navigation"},
384 {ax::mojom::Role::kNote, "Note"},
385 {ax::mojom::Role::kPane, "Pane"},
386 {ax::mojom::Role::kParagraph, "Paragraph"},
387 {ax::mojom::Role::kPdfActionableHighlight, "PdfActionableHighlight"},
388 {ax::mojom::Role::kPluginObject, "PluginObject"},
389 {ax::mojom::Role::kPopUpButton, "PopUpButton"},
390 {ax::mojom::Role::kPortal, "Portal"},
391 {ax::mojom::Role::kPre, "Pre"},
392 {ax::mojom::Role::kPresentational, "Presentational"},
393 {ax::mojom::Role::kProgressIndicator, "ProgressIndicator"},
394 {ax::mojom::Role::kRadioButton, "RadioButton"},
395 {ax::mojom::Role::kRadioGroup, "RadioGroup"},
396 {ax::mojom::Role::kRegion, "Region"},
397 {ax::mojom::Role::kRootWebArea, "WebArea"},
398 {ax::mojom::Role::kRow, "Row"},
399 {ax::mojom::Role::kRowGroup, "RowGroup"},
400 {ax::mojom::Role::kRowHeader, "RowHeader"},
401 {ax::mojom::Role::kRuby, "Ruby"},
402 {ax::mojom::Role::kRubyAnnotation, "RubyAnnotation"},
403 {ax::mojom::Role::kSection, "Section"},
404 {ax::mojom::Role::kSvgRoot, "SVGRoot"},
405 {ax::mojom::Role::kScrollBar, "ScrollBar"},
406 {ax::mojom::Role::kScrollView, "ScrollView"},
407 {ax::mojom::Role::kSearch, "Search"},
408 {ax::mojom::Role::kSearchBox, "SearchBox"},
409 {ax::mojom::Role::kSlider, "Slider"},
410 {ax::mojom::Role::kSliderThumb, "SliderThumb"},
411 {ax::mojom::Role::kSpinButton, "SpinButton"},
412 {ax::mojom::Role::kSplitter, "Splitter"},
413 {ax::mojom::Role::kStaticText, "StaticText"},
414 {ax::mojom::Role::kStatus, "Status"},
415 {ax::mojom::Role::kStrong, "Strong"},
416 {ax::mojom::Role::kSuggestion, "Suggestion"},
417 {ax::mojom::Role::kSwitch, "Switch"},
418 {ax::mojom::Role::kTab, "Tab"},
419 {ax::mojom::Role::kTabList, "TabList"},
420 {ax::mojom::Role::kTabPanel, "TabPanel"},
421 {ax::mojom::Role::kTable, "Table"},
422 {ax::mojom::Role::kTableHeaderContainer, "TableHeaderContainer"},
423 {ax::mojom::Role::kTerm, "Term"},
424 {ax::mojom::Role::kTextField, "TextField"},
425 {ax::mojom::Role::kTextFieldWithComboBox, "ComboBox"},
426 {ax::mojom::Role::kTime, "Time"},
427 {ax::mojom::Role::kTimer, "Timer"},
428 {ax::mojom::Role::kTitleBar, "TitleBar"},
429 {ax::mojom::Role::kToggleButton, "ToggleButton"},
430 {ax::mojom::Role::kToolbar, "Toolbar"},
431 {ax::mojom::Role::kTreeGrid, "TreeGrid"},
432 {ax::mojom::Role::kTreeItem, "TreeItem"},
433 {ax::mojom::Role::kTree, "Tree"},
434 {ax::mojom::Role::kTooltip, "UserInterfaceTooltip"},
435 {ax::mojom::Role::kUnknown, "Unknown"},
436 {ax::mojom::Role::kVideo, "Video"},
437 {ax::mojom::Role::kWebArea, "WebArea"},
438 {ax::mojom::Role::kWebView, "WebView"},
439 {ax::mojom::Role::kWindow, "Window"}};
440
441 static_assert(base::size(kInternalRoles) ==
442 static_cast<size_t>(ax::mojom::Role::kMaxValue) + 1,
443 "Not all internal roles have an entry in internalRoles array");
444
445 // Roles which we need to map in the other direction
446 const RoleEntry kReverseRoles[] = {
447 {"banner", ax::mojom::Role::kHeader},
448 {"button", ax::mojom::Role::kToggleButton},
449 {"combobox", ax::mojom::Role::kPopUpButton},
450 {"contentinfo", ax::mojom::Role::kFooter},
451 {"menuitem", ax::mojom::Role::kMenuButton},
452 {"menuitem", ax::mojom::Role::kMenuListOption},
453 {"progressbar", ax::mojom::Role::kMeter},
454 {"region", ax::mojom::Role::kSection},
455 {"textbox", ax::mojom::Role::kTextField},
456 {"combobox", ax::mojom::Role::kComboBoxMenuButton},
457 {"combobox", ax::mojom::Role::kTextFieldWithComboBox}};
458
CreateARIARoleMap()459 static ARIARoleMap* CreateARIARoleMap() {
460 ARIARoleMap* role_map = new ARIARoleMap;
461
462 for (size_t i = 0; i < base::size(kRoles); ++i)
463 role_map->Set(String(kRoles[i].aria_role), kRoles[i].webcore_role);
464
465 return role_map;
466 }
467
CreateRoleNameVector()468 static Vector<AtomicString>* CreateRoleNameVector() {
469 Vector<AtomicString>* role_name_vector =
470 new Vector<AtomicString>(base::size(kInternalRoles));
471 for (wtf_size_t i = 0; i < base::size(kInternalRoles); i++)
472 (*role_name_vector)[i] = g_null_atom;
473
474 for (wtf_size_t i = 0; i < base::size(kRoles); ++i) {
475 (*role_name_vector)[static_cast<wtf_size_t>(kRoles[i].webcore_role)] =
476 AtomicString(kRoles[i].aria_role);
477 }
478
479 for (wtf_size_t i = 0; i < base::size(kReverseRoles); ++i) {
480 (*role_name_vector)[static_cast<wtf_size_t>(
481 kReverseRoles[i].webcore_role)] =
482 AtomicString(kReverseRoles[i].aria_role);
483 }
484
485 return role_name_vector;
486 }
487
CreateInternalRoleNameVector()488 static Vector<AtomicString>* CreateInternalRoleNameVector() {
489 Vector<AtomicString>* internal_role_name_vector =
490 new Vector<AtomicString>(base::size(kInternalRoles));
491 for (wtf_size_t i = 0; i < base::size(kInternalRoles); i++) {
492 (*internal_role_name_vector)[static_cast<wtf_size_t>(
493 kInternalRoles[i].webcore_role)] =
494 AtomicString(kInternalRoles[i].internal_role_name);
495 }
496
497 return internal_role_name_vector;
498 }
499
GetActiveDialogElement(Node * node)500 HTMLDialogElement* GetActiveDialogElement(Node* node) {
501 return node->GetDocument().ActiveModalDialog();
502 }
503
504 } // namespace
505
506 unsigned AXObject::number_of_live_ax_objects_ = 0;
507
AXObject(AXObjectCacheImpl & ax_object_cache)508 AXObject::AXObject(AXObjectCacheImpl& ax_object_cache)
509 : id_(0),
510 have_children_(false),
511 role_(ax::mojom::Role::kUnknown),
512 aria_role_(ax::mojom::Role::kUnknown),
513 last_known_is_ignored_value_(kDefaultBehavior),
514 last_known_is_ignored_but_included_in_tree_value_(kDefaultBehavior),
515 explicit_container_id_(0),
516 parent_(nullptr),
517 last_modification_count_(-1),
518 cached_is_ignored_(false),
519 cached_is_ignored_but_included_in_tree_(false),
520 cached_is_inert_or_aria_hidden_(false),
521 cached_is_descendant_of_leaf_node_(false),
522 cached_is_descendant_of_disabled_node_(false),
523 cached_has_inherited_presentational_role_(false),
524 cached_is_editable_root_(false),
525 cached_live_region_root_(nullptr),
526 cached_aria_column_index_(0),
527 cached_aria_row_index_(0),
528 ax_object_cache_(&ax_object_cache) {
529 ++number_of_live_ax_objects_;
530 }
531
~AXObject()532 AXObject::~AXObject() {
533 DCHECK(IsDetached());
534 --number_of_live_ax_objects_;
535 }
536
Init()537 void AXObject::Init() {
538 role_ = DetermineAccessibilityRole();
539 }
540
Detach()541 void AXObject::Detach() {
542 // Clear any children and call detachFromParent on them so that
543 // no children are left with dangling pointers to their parent.
544 ClearChildren();
545
546 ax_object_cache_ = nullptr;
547 }
548
IsDetached() const549 bool AXObject::IsDetached() const {
550 return !ax_object_cache_;
551 }
552
GetAOMPropertyOrARIAAttribute(AOMStringProperty property) const553 const AtomicString& AXObject::GetAOMPropertyOrARIAAttribute(
554 AOMStringProperty property) const {
555 Element* element = this->GetElement();
556 if (!element)
557 return g_null_atom;
558
559 return AccessibleNode::GetPropertyOrARIAAttribute(element, property);
560 }
561
GetAOMPropertyOrARIAAttribute(AOMRelationProperty property) const562 Element* AXObject::GetAOMPropertyOrARIAAttribute(
563 AOMRelationProperty property) const {
564 Element* element = this->GetElement();
565 if (!element)
566 return nullptr;
567
568 return AccessibleNode::GetPropertyOrARIAAttribute(element, property);
569 }
570
HasAOMProperty(AOMRelationListProperty property,HeapVector<Member<Element>> & result) const571 bool AXObject::HasAOMProperty(AOMRelationListProperty property,
572 HeapVector<Member<Element>>& result) const {
573 Element* element = this->GetElement();
574 if (!element)
575 return false;
576
577 return AccessibleNode::GetProperty(element, property, result);
578 }
579
HasAOMPropertyOrARIAAttribute(AOMRelationListProperty property,HeapVector<Member<Element>> & result) const580 bool AXObject::HasAOMPropertyOrARIAAttribute(
581 AOMRelationListProperty property,
582 HeapVector<Member<Element>>& result) const {
583 Element* element = this->GetElement();
584 if (!element)
585 return false;
586
587 return AccessibleNode::GetPropertyOrARIAAttribute(element, property, result);
588 }
589
HasAOMPropertyOrARIAAttribute(AOMBooleanProperty property,bool & result) const590 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMBooleanProperty property,
591 bool& result) const {
592 Element* element = this->GetElement();
593 if (!element)
594 return false;
595
596 bool is_null = true;
597 result =
598 AccessibleNode::GetPropertyOrARIAAttribute(element, property, is_null);
599 return !is_null;
600 }
601
AOMPropertyOrARIAAttributeIsTrue(AOMBooleanProperty property) const602 bool AXObject::AOMPropertyOrARIAAttributeIsTrue(
603 AOMBooleanProperty property) const {
604 bool result;
605 if (HasAOMPropertyOrARIAAttribute(property, result))
606 return result;
607 return false;
608 }
609
AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty property) const610 bool AXObject::AOMPropertyOrARIAAttributeIsFalse(
611 AOMBooleanProperty property) const {
612 bool result;
613 if (HasAOMPropertyOrARIAAttribute(property, result))
614 return !result;
615 return false;
616 }
617
HasAOMPropertyOrARIAAttribute(AOMUIntProperty property,uint32_t & result) const618 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMUIntProperty property,
619 uint32_t& result) const {
620 Element* element = this->GetElement();
621 if (!element)
622 return false;
623
624 bool is_null = true;
625 result =
626 AccessibleNode::GetPropertyOrARIAAttribute(element, property, is_null);
627 return !is_null;
628 }
629
HasAOMPropertyOrARIAAttribute(AOMIntProperty property,int32_t & result) const630 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMIntProperty property,
631 int32_t& result) const {
632 Element* element = this->GetElement();
633 if (!element)
634 return false;
635
636 bool is_null = true;
637 result =
638 AccessibleNode::GetPropertyOrARIAAttribute(element, property, is_null);
639 return !is_null;
640 }
641
HasAOMPropertyOrARIAAttribute(AOMFloatProperty property,float & result) const642 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMFloatProperty property,
643 float& result) const {
644 Element* element = this->GetElement();
645 if (!element)
646 return false;
647
648 bool is_null = true;
649 result =
650 AccessibleNode::GetPropertyOrARIAAttribute(element, property, is_null);
651 return !is_null;
652 }
653
HasAOMPropertyOrARIAAttribute(AOMStringProperty property,AtomicString & result) const654 bool AXObject::HasAOMPropertyOrARIAAttribute(AOMStringProperty property,
655 AtomicString& result) const {
656 Element* element = this->GetElement();
657 if (!element)
658 return false;
659
660 result = AccessibleNode::GetPropertyOrARIAAttribute(element, property);
661 return !result.IsNull();
662 }
663
GetAccessibleNode() const664 AccessibleNode* AXObject::GetAccessibleNode() const {
665 Element* element = GetElement();
666 if (!element)
667 return nullptr;
668
669 return element->ExistingAccessibleNode();
670 }
671
GetSparseAXAttributes(AXSparseAttributeClient & sparse_attribute_client) const672 void AXObject::GetSparseAXAttributes(
673 AXSparseAttributeClient& sparse_attribute_client) const {
674 AXSparseAttributeAOMPropertyClient property_client(*ax_object_cache_,
675 sparse_attribute_client);
676 HashSet<QualifiedName> shadowed_aria_attributes;
677 AccessibleNode* accessible_node = GetAccessibleNode();
678
679 // Virtual nodes for AOM are still tied to the AXTree.
680 if (accessible_node && IsVirtualObject())
681 accessible_node->GetAllAOMProperties(&property_client,
682 shadowed_aria_attributes);
683
684 Element* element = GetElement();
685 if (!element)
686 return;
687
688 AXSparseAttributeSetterMap& ax_sparse_attribute_setter_map =
689 GetSparseAttributeSetterMap();
690 AttributeCollection attributes = element->AttributesWithoutUpdate();
691 HashSet<QualifiedName> set_attributes;
692 for (const Attribute& attr : attributes) {
693 set_attributes.insert(attr.GetName());
694 if (shadowed_aria_attributes.Contains(attr.GetName()))
695 continue;
696
697 AXSparseAttributeSetter* setter =
698 ax_sparse_attribute_setter_map.at(attr.GetName());
699 if (setter)
700 setter->Run(*this, sparse_attribute_client, attr.Value());
701 }
702 if (!element->DidAttachInternals())
703 return;
704 const auto& internals_attributes =
705 element->EnsureElementInternals().GetAttributes();
706 for (const QualifiedName& attr : internals_attributes.Keys()) {
707 if (set_attributes.Contains(attr))
708 continue;
709 AXSparseAttributeSetter* setter = ax_sparse_attribute_setter_map.at(attr);
710 if (setter) {
711 setter->Run(*this, sparse_attribute_client,
712 internals_attributes.at(attr));
713 }
714 }
715 }
716
IsARIATextControl() const717 bool AXObject::IsARIATextControl() const {
718 return AriaRoleAttribute() == ax::mojom::Role::kTextField ||
719 AriaRoleAttribute() == ax::mojom::Role::kSearchBox ||
720 AriaRoleAttribute() == ax::mojom::Role::kTextFieldWithComboBox;
721 }
722
IsButton() const723 bool AXObject::IsButton() const {
724 ax::mojom::Role role = RoleValue();
725
726 return role == ax::mojom::Role::kButton ||
727 role == ax::mojom::Role::kPopUpButton ||
728 role == ax::mojom::Role::kToggleButton;
729 }
730
IsCheckable() const731 bool AXObject::IsCheckable() const {
732 switch (RoleValue()) {
733 case ax::mojom::Role::kCheckBox:
734 case ax::mojom::Role::kMenuItemCheckBox:
735 case ax::mojom::Role::kMenuItemRadio:
736 case ax::mojom::Role::kRadioButton:
737 case ax::mojom::Role::kSwitch:
738 case ax::mojom::Role::kToggleButton:
739 return true;
740 case ax::mojom::Role::kTreeItem:
741 case ax::mojom::Role::kListBoxOption:
742 case ax::mojom::Role::kMenuListOption:
743 return AriaCheckedIsPresent();
744 default:
745 return false;
746 }
747 }
748
749 // Why this is here instead of AXNodeObject:
750 // Because an AXMenuListOption (<option>) can
751 // have an ARIA role of menuitemcheckbox/menuitemradio
752 // yet does not inherit from AXNodeObject
CheckedState() const753 ax::mojom::CheckedState AXObject::CheckedState() const {
754 if (!IsCheckable())
755 return ax::mojom::CheckedState::kNone;
756
757 // Try ARIA checked/pressed state
758 const ax::mojom::Role role = RoleValue();
759 const auto prop = role == ax::mojom::Role::kToggleButton
760 ? AOMStringProperty::kPressed
761 : AOMStringProperty::kChecked;
762 const AtomicString& checked_attribute = GetAOMPropertyOrARIAAttribute(prop);
763 if (checked_attribute) {
764 if (EqualIgnoringASCIICase(checked_attribute, "mixed")) {
765 // Only checkable role that doesn't support mixed is the switch.
766 if (role != ax::mojom::Role::kSwitch)
767 return ax::mojom::CheckedState::kMixed;
768 }
769
770 // Anything other than "false" should be treated as "true".
771 return EqualIgnoringASCIICase(checked_attribute, "false")
772 ? ax::mojom::CheckedState::kFalse
773 : ax::mojom::CheckedState::kTrue;
774 }
775
776 // Native checked state
777 if (role != ax::mojom::Role::kToggleButton) {
778 const Node* node = this->GetNode();
779 if (!node)
780 return ax::mojom::CheckedState::kNone;
781
782 // Expose native checkbox mixed state as accessibility mixed state. However,
783 // do not expose native radio mixed state as accessibility mixed state.
784 // This would confuse the JAWS screen reader, which reports a mixed radio as
785 // both checked and partially checked, but a native mixed native radio
786 // button sinply means no radio buttons have been checked in the group yet.
787 if (IsNativeCheckboxInMixedState(node))
788 return ax::mojom::CheckedState::kMixed;
789
790 auto* html_input_element = DynamicTo<HTMLInputElement>(node);
791 if (html_input_element && html_input_element->ShouldAppearChecked()) {
792 return ax::mojom::CheckedState::kTrue;
793 }
794 }
795
796 return ax::mojom::CheckedState::kFalse;
797 }
798
IsNativeCheckboxInMixedState(const Node * node)799 bool AXObject::IsNativeCheckboxInMixedState(const Node* node) {
800 const auto* input = DynamicTo<HTMLInputElement>(node);
801 if (!input)
802 return false;
803
804 const auto inputType = input->type();
805 if (inputType != input_type_names::kCheckbox)
806 return false;
807 return input->ShouldAppearIndeterminate();
808 }
809
IsLandmarkRelated() const810 bool AXObject::IsLandmarkRelated() const {
811 switch (RoleValue()) {
812 case ax::mojom::Role::kApplication:
813 case ax::mojom::Role::kArticle:
814 case ax::mojom::Role::kBanner:
815 case ax::mojom::Role::kComplementary:
816 case ax::mojom::Role::kContentInfo:
817 case ax::mojom::Role::kDocAcknowledgments:
818 case ax::mojom::Role::kDocAfterword:
819 case ax::mojom::Role::kDocAppendix:
820 case ax::mojom::Role::kDocBibliography:
821 case ax::mojom::Role::kDocChapter:
822 case ax::mojom::Role::kDocConclusion:
823 case ax::mojom::Role::kDocCredits:
824 case ax::mojom::Role::kDocEndnotes:
825 case ax::mojom::Role::kDocEpilogue:
826 case ax::mojom::Role::kDocErrata:
827 case ax::mojom::Role::kDocForeword:
828 case ax::mojom::Role::kDocGlossary:
829 case ax::mojom::Role::kDocIntroduction:
830 case ax::mojom::Role::kDocPart:
831 case ax::mojom::Role::kDocPreface:
832 case ax::mojom::Role::kDocPrologue:
833 case ax::mojom::Role::kDocToc:
834 case ax::mojom::Role::kFooter:
835 case ax::mojom::Role::kForm:
836 case ax::mojom::Role::kHeader:
837 case ax::mojom::Role::kMain:
838 case ax::mojom::Role::kNavigation:
839 case ax::mojom::Role::kRegion:
840 case ax::mojom::Role::kSearch:
841 case ax::mojom::Role::kSection:
842 return true;
843 default:
844 return false;
845 }
846 }
847
IsMenuRelated() const848 bool AXObject::IsMenuRelated() const {
849 switch (RoleValue()) {
850 case ax::mojom::Role::kMenu:
851 case ax::mojom::Role::kMenuBar:
852 case ax::mojom::Role::kMenuButton:
853 case ax::mojom::Role::kMenuItem:
854 case ax::mojom::Role::kMenuItemCheckBox:
855 case ax::mojom::Role::kMenuItemRadio:
856 return true;
857 default:
858 return false;
859 }
860 }
861
IsPasswordFieldAndShouldHideValue() const862 bool AXObject::IsPasswordFieldAndShouldHideValue() const {
863 Settings* settings = GetDocument()->GetSettings();
864 if (!settings || settings->GetAccessibilityPasswordValuesEnabled())
865 return false;
866
867 return IsPasswordField();
868 }
869
IsTextObject() const870 bool AXObject::IsTextObject() const {
871 // Objects with |ax::mojom::Role::kLineBreak| are HTML <br> elements and are
872 // not backed by DOM text nodes. We can't mark them as text objects for that
873 // reason.
874 switch (RoleValue()) {
875 case ax::mojom::Role::kInlineTextBox:
876 case ax::mojom::Role::kStaticText:
877 return true;
878 default:
879 return false;
880 }
881 }
882
IsClickable() const883 bool AXObject::IsClickable() const {
884 if (IsButton() || IsLink() || IsTextControl())
885 return true;
886
887 // TODO(dmazzoni): Ensure that ax::mojom::Role::kColorWell and
888 // ax::mojom::Role::kSpinButton are correctly handled here via their
889 // constituent parts.
890 switch (RoleValue()) {
891 case ax::mojom::Role::kCheckBox:
892 case ax::mojom::Role::kComboBoxMenuButton:
893 case ax::mojom::Role::kDisclosureTriangle:
894 case ax::mojom::Role::kListBox:
895 case ax::mojom::Role::kListBoxOption:
896 case ax::mojom::Role::kMenuItemCheckBox:
897 case ax::mojom::Role::kMenuItemRadio:
898 case ax::mojom::Role::kMenuItem:
899 case ax::mojom::Role::kMenuListOption:
900 case ax::mojom::Role::kRadioButton:
901 case ax::mojom::Role::kSwitch:
902 case ax::mojom::Role::kTab:
903 return true;
904 default:
905 return false;
906 }
907 }
908
AccessibilityIsIgnored() const909 bool AXObject::AccessibilityIsIgnored() const {
910 UpdateDistributionForFlatTreeTraversal();
911 UpdateCachedAttributeValuesIfNeeded();
912 return cached_is_ignored_;
913 }
914
AccessibilityIsIgnoredButIncludedInTree() const915 bool AXObject::AccessibilityIsIgnoredButIncludedInTree() const {
916 UpdateDistributionForFlatTreeTraversal();
917 UpdateCachedAttributeValuesIfNeeded();
918 return cached_is_ignored_but_included_in_tree_;
919 }
920
921 // AccessibilityIsIncludedInTree should be true for all nodes that should be
922 // included in the tree, even if they are ignored
AccessibilityIsIncludedInTree() const923 bool AXObject::AccessibilityIsIncludedInTree() const {
924 return !AccessibilityIsIgnored() || AccessibilityIsIgnoredButIncludedInTree();
925 }
926
UpdateCachedAttributeValuesIfNeeded() const927 void AXObject::UpdateCachedAttributeValuesIfNeeded() const {
928 if (IsDetached())
929 return;
930
931 AXObjectCacheImpl& cache = AXObjectCache();
932
933 if (cache.ModificationCount() == last_modification_count_)
934 return;
935
936 last_modification_count_ = cache.ModificationCount();
937 cached_background_color_ = ComputeBackgroundColor();
938 cached_is_inert_or_aria_hidden_ = ComputeIsInertOrAriaHidden();
939 cached_is_descendant_of_leaf_node_ = !!LeafNodeAncestor();
940 cached_is_descendant_of_disabled_node_ = !!DisabledAncestor();
941 cached_has_inherited_presentational_role_ =
942 !!InheritsPresentationalRoleFrom();
943 cached_is_ignored_ = ComputeAccessibilityIsIgnored();
944 cached_is_ignored_but_included_in_tree_ =
945 cached_is_ignored_ && ComputeAccessibilityIsIgnoredButIncludedInTree();
946 cached_is_editable_root_ = ComputeIsEditableRoot();
947 // Compute live region root, which can be from any ARIA live value, including
948 // "off", or from an automatic ARIA live value, e.g. from role="status".
949 // TODO(dmazzoni): remove this const_cast.
950 AtomicString aria_live;
951 cached_live_region_root_ =
952 IsLiveRegionRoot()
953 ? const_cast<AXObject*>(this)
954 : (ParentObjectIfExists() ? ParentObjectIfExists()->LiveRegionRoot()
955 : nullptr);
956 cached_aria_column_index_ = ComputeAriaColumnIndex();
957 cached_aria_row_index_ = ComputeAriaRowIndex();
958
959 bool ignored_states_changed = false;
960 if (cached_is_ignored_ != LastKnownIsIgnoredValue()) {
961 last_known_is_ignored_value_ =
962 cached_is_ignored_ ? kIgnoreObject : kIncludeObject;
963 ignored_states_changed = true;
964 }
965
966 if (cached_is_ignored_but_included_in_tree_ !=
967 LastKnownIsIgnoredButIncludedInTreeValue()) {
968 last_known_is_ignored_but_included_in_tree_value_ =
969 cached_is_ignored_but_included_in_tree_ ? kIncludeObject
970 : kIgnoreObject;
971 ignored_states_changed = true;
972 }
973
974 if (ignored_states_changed) {
975 if (AXObject* parent = ParentObjectIfExists())
976 parent->ChildrenChanged();
977 }
978
979 if (GetLayoutObject() && GetLayoutObject()->IsText()) {
980 cached_local_bounding_box_rect_for_accessibility_ =
981 GetLayoutObject()->LocalBoundingBoxRectForAccessibility();
982 }
983 }
984
AccessibilityIsIgnoredByDefault(IgnoredReasons * ignored_reasons) const985 bool AXObject::AccessibilityIsIgnoredByDefault(
986 IgnoredReasons* ignored_reasons) const {
987 return DefaultObjectInclusion(ignored_reasons) == kIgnoreObject;
988 }
989
AccessibilityPlatformIncludesObject() const990 AXObjectInclusion AXObject::AccessibilityPlatformIncludesObject() const {
991 if (IsA<AXMenuListPopup>(this) || IsA<AXMenuListOption>(this))
992 return kIncludeObject;
993
994 return kDefaultBehavior;
995 }
996
DefaultObjectInclusion(IgnoredReasons * ignored_reasons) const997 AXObjectInclusion AXObject::DefaultObjectInclusion(
998 IgnoredReasons* ignored_reasons) const {
999 if (IsInertOrAriaHidden()) {
1000 // Keep focusable elements that are aria-hidden in tree, so that they can
1001 // still fire events such as focus and value changes.
1002 const Element* elem = GetElement();
1003 if (!elem || !elem->SupportsFocus() || elem->IsInert()) {
1004 if (ignored_reasons)
1005 ComputeIsInertOrAriaHidden(ignored_reasons);
1006 return kIgnoreObject;
1007 }
1008 }
1009
1010 return AccessibilityPlatformIncludesObject();
1011 }
1012
IsInertOrAriaHidden() const1013 bool AXObject::IsInertOrAriaHidden() const {
1014 UpdateCachedAttributeValuesIfNeeded();
1015 return cached_is_inert_or_aria_hidden_;
1016 }
1017
ComputeIsInertOrAriaHidden(IgnoredReasons * ignored_reasons) const1018 bool AXObject::ComputeIsInertOrAriaHidden(
1019 IgnoredReasons* ignored_reasons) const {
1020 if (GetNode()) {
1021 if (GetNode()->IsInert()) {
1022 if (ignored_reasons) {
1023 HTMLDialogElement* dialog = GetActiveDialogElement(GetNode());
1024 if (dialog) {
1025 AXObject* dialog_object = AXObjectCache().GetOrCreate(dialog);
1026 if (dialog_object) {
1027 ignored_reasons->push_back(
1028 IgnoredReason(kAXActiveModalDialog, dialog_object));
1029 } else {
1030 ignored_reasons->push_back(IgnoredReason(kAXInertElement));
1031 }
1032 } else {
1033 const AXObject* inert_root_el = InertRoot();
1034 if (inert_root_el == this) {
1035 ignored_reasons->push_back(IgnoredReason(kAXInertElement));
1036 } else {
1037 ignored_reasons->push_back(
1038 IgnoredReason(kAXInertSubtree, inert_root_el));
1039 }
1040 }
1041 }
1042 return true;
1043 }
1044 } else {
1045 AXObject* parent = ParentObject();
1046 if (parent && parent->IsInertOrAriaHidden()) {
1047 if (ignored_reasons)
1048 parent->ComputeIsInertOrAriaHidden(ignored_reasons);
1049 return true;
1050 }
1051 }
1052
1053 const AXObject* hidden_root = AriaHiddenRoot();
1054 if (hidden_root) {
1055 if (ignored_reasons) {
1056 if (hidden_root == this) {
1057 ignored_reasons->push_back(IgnoredReason(kAXAriaHiddenElement));
1058 } else {
1059 ignored_reasons->push_back(
1060 IgnoredReason(kAXAriaHiddenSubtree, hidden_root));
1061 }
1062 }
1063 return true;
1064 }
1065
1066 return false;
1067 }
1068
IsVisible() const1069 bool AXObject::IsVisible() const {
1070 return !IsInertOrAriaHidden() && !IsHiddenViaStyle();
1071 }
1072
IsDescendantOfLeafNode() const1073 bool AXObject::IsDescendantOfLeafNode() const {
1074 UpdateCachedAttributeValuesIfNeeded();
1075 return cached_is_descendant_of_leaf_node_;
1076 }
1077
LeafNodeAncestor() const1078 AXObject* AXObject::LeafNodeAncestor() const {
1079 if (AXObject* parent = ParentObject()) {
1080 if (!parent->CanHaveChildren())
1081 return parent;
1082
1083 return parent->LeafNodeAncestor();
1084 }
1085
1086 return nullptr;
1087 }
1088
AriaHiddenRoot() const1089 const AXObject* AXObject::AriaHiddenRoot() const {
1090 for (const AXObject* object = this; object; object = object->ParentObject()) {
1091 if (object->AOMPropertyOrARIAAttributeIsTrue(AOMBooleanProperty::kHidden))
1092 return object;
1093 }
1094
1095 return nullptr;
1096 }
1097
InertRoot() const1098 const AXObject* AXObject::InertRoot() const {
1099 const AXObject* object = this;
1100 if (!RuntimeEnabledFeatures::InertAttributeEnabled())
1101 return nullptr;
1102
1103 while (object && !object->IsAXNodeObject())
1104 object = object->ParentObject();
1105 Node* node = object->GetNode();
1106 auto* element = DynamicTo<Element>(node);
1107 if (!element)
1108 element = FlatTreeTraversal::ParentElement(*node);
1109
1110 while (element) {
1111 if (element->FastHasAttribute(html_names::kInertAttr))
1112 return AXObjectCache().GetOrCreate(element);
1113 element = FlatTreeTraversal::ParentElement(*element);
1114 }
1115
1116 return nullptr;
1117 }
1118
DispatchEventToAOMEventListeners(Event & event)1119 bool AXObject::DispatchEventToAOMEventListeners(Event& event) {
1120 HeapVector<Member<AccessibleNode>> event_path;
1121 for (AXObject* ancestor = this; ancestor;
1122 ancestor = ancestor->ParentObject()) {
1123 AccessibleNode* ancestor_accessible_node = ancestor->GetAccessibleNode();
1124 if (!ancestor_accessible_node)
1125 continue;
1126
1127 if (!ancestor_accessible_node->HasEventListeners(event.type()))
1128 continue;
1129
1130 event_path.push_back(ancestor_accessible_node);
1131 }
1132
1133 // Short-circuit: if there are no AccessibleNodes attached anywhere
1134 // in the ancestry of this node, exit.
1135 if (!event_path.size())
1136 return false;
1137
1138 // Check if the user has granted permission for this domain to use
1139 // AOM event listeners yet. This may trigger an infobar, but we shouldn't
1140 // block, so whatever decision the user makes will apply to the next
1141 // event received after that.
1142 //
1143 // Note that we only ask the user about this permission the first
1144 // time an event is received that actually would have triggered an
1145 // event listener. However, if the user grants this permission, it
1146 // persists for this origin from then on.
1147 if (!AXObjectCache().CanCallAOMEventListeners()) {
1148 AXObjectCache().RequestAOMEventListenerPermission();
1149 return false;
1150 }
1151
1152 // Since we now know the AOM is being used in this document, get the
1153 // AccessibleNode for the target element and create it if necessary -
1154 // otherwise we wouldn't be able to set the event target. However note
1155 // that if it didn't previously exist it won't be part of the event path.
1156 AccessibleNode* target = GetAccessibleNode();
1157 if (!target) {
1158 if (Element* element = GetElement())
1159 target = element->accessibleNode();
1160 }
1161 if (!target)
1162 return false;
1163 event.SetTarget(target);
1164
1165 // Capturing phase.
1166 event.SetEventPhase(Event::kCapturingPhase);
1167 for (int i = static_cast<int>(event_path.size()) - 1; i >= 0; i--) {
1168 // Don't call capturing event listeners on the target. Note that
1169 // the target may not necessarily be in the event path which is why
1170 // we check here.
1171 if (event_path[i] == target)
1172 break;
1173
1174 event.SetCurrentTarget(event_path[i]);
1175 event_path[i]->FireEventListeners(event);
1176 if (event.PropagationStopped())
1177 return true;
1178 }
1179
1180 // Targeting phase.
1181 event.SetEventPhase(Event::kAtTarget);
1182 event.SetCurrentTarget(event_path[0]);
1183 event_path[0]->FireEventListeners(event);
1184 if (event.PropagationStopped())
1185 return true;
1186
1187 // Bubbling phase.
1188 event.SetEventPhase(Event::kBubblingPhase);
1189 for (wtf_size_t i = 1; i < event_path.size(); i++) {
1190 event.SetCurrentTarget(event_path[i]);
1191 event_path[i]->FireEventListeners(event);
1192 if (event.PropagationStopped())
1193 return true;
1194 }
1195
1196 if (event.defaultPrevented())
1197 return true;
1198
1199 return false;
1200 }
1201
IsDescendantOfDisabledNode() const1202 bool AXObject::IsDescendantOfDisabledNode() const {
1203 UpdateCachedAttributeValuesIfNeeded();
1204 return cached_is_descendant_of_disabled_node_;
1205 }
1206
DisabledAncestor() const1207 const AXObject* AXObject::DisabledAncestor() const {
1208 bool disabled = false;
1209 if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kDisabled, disabled)) {
1210 if (disabled)
1211 return this;
1212 return nullptr;
1213 }
1214
1215 if (AXObject* parent = ParentObject())
1216 return parent->DisabledAncestor();
1217
1218 return nullptr;
1219 }
1220
ComputeAccessibilityIsIgnoredButIncludedInTree() const1221 bool AXObject::ComputeAccessibilityIsIgnoredButIncludedInTree() const {
1222 if (!GetNode())
1223 return false;
1224
1225 // Use a flag to control whether or not the <html> element is included
1226 // in the accessibility tree. Either way it's always marked as "ignored",
1227 // but eventually we want to always include it in the tree to simplify
1228 // some logic.
1229 if (GetNode() && IsA<HTMLHtmlElement>(GetNode()))
1230 return RuntimeEnabledFeatures::AccessibilityExposeHTMLElementEnabled();
1231
1232 // If the node is part of the user agent shadow dom, or has the explicit
1233 // internal Role::kIgnored, they aren't interesting for paragraph navigation
1234 // or LabelledBy/DescribedBy relationships.
1235 if (RoleValue() == ax::mojom::Role::kIgnored ||
1236 GetNode()->IsInUserAgentShadowRoot()) {
1237 return false;
1238 }
1239
1240 // Always pass through Line Breaking objects, this is necessary to
1241 // detect paragraph edges, which are defined as hard-line breaks.
1242 if (IsLineBreakingObject())
1243 return true;
1244
1245 // Allow the browser side ax tree to access "visibility: [hidden|collapse]"
1246 // and "display: none" nodes. This is useful for APIs that return the node
1247 // referenced by aria-labeledby and aria-describedby.
1248 // An element must have an id attribute or it cannot be referenced by
1249 // aria-labelledby or aria-describedby.
1250 if (RuntimeEnabledFeatures::AccessibilityExposeDisplayNoneEnabled()) {
1251 if (Element* element = GetElement()) {
1252 if (element->FastHasAttribute(html_names::kIdAttr) &&
1253 IsHiddenViaStyle()) {
1254 return true;
1255 }
1256 }
1257 } else if (GetLayoutObject()) {
1258 if (GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible)
1259 return true;
1260 }
1261
1262 // Allow the browser side ax tree to access "aria-hidden" nodes.
1263 // This is useful for APIs that return the node referenced by
1264 // aria-labeledby and aria-describedby.
1265 if (GetLayoutObject() && AriaHiddenRoot())
1266 return true;
1267
1268 return false;
1269 }
1270
DatetimeAncestor(int max_levels_to_check) const1271 const AXObject* AXObject::DatetimeAncestor(int max_levels_to_check) const {
1272 switch (RoleValue()) {
1273 case ax::mojom::Role::kDateTime:
1274 case ax::mojom::Role::kDate:
1275 case ax::mojom::Role::kInputTime:
1276 case ax::mojom::Role::kTime:
1277 return this;
1278 default:
1279 break;
1280 }
1281
1282 if (max_levels_to_check == 0)
1283 return nullptr;
1284
1285 if (AXObject* parent = ParentObject())
1286 return parent->DatetimeAncestor(max_levels_to_check - 1);
1287
1288 return nullptr;
1289 }
1290
LastKnownIsIgnoredValue() const1291 bool AXObject::LastKnownIsIgnoredValue() const {
1292 if (last_known_is_ignored_value_ == kDefaultBehavior) {
1293 last_known_is_ignored_value_ =
1294 AccessibilityIsIgnored() ? kIgnoreObject : kIncludeObject;
1295 }
1296
1297 return last_known_is_ignored_value_ == kIgnoreObject;
1298 }
1299
SetLastKnownIsIgnoredValue(bool is_ignored)1300 void AXObject::SetLastKnownIsIgnoredValue(bool is_ignored) {
1301 last_known_is_ignored_value_ = is_ignored ? kIgnoreObject : kIncludeObject;
1302 }
1303
LastKnownIsIgnoredButIncludedInTreeValue() const1304 bool AXObject::LastKnownIsIgnoredButIncludedInTreeValue() const {
1305 if (last_known_is_ignored_but_included_in_tree_value_ == kDefaultBehavior) {
1306 last_known_is_ignored_but_included_in_tree_value_ =
1307 AccessibilityIsIgnoredButIncludedInTree() ? kIncludeObject
1308 : kIgnoreObject;
1309 }
1310
1311 return last_known_is_ignored_but_included_in_tree_value_ == kIncludeObject;
1312 }
1313
SetLastKnownIsIgnoredButIncludedInTreeValue(bool is_ignored_but_included_in_tree)1314 void AXObject::SetLastKnownIsIgnoredButIncludedInTreeValue(
1315 bool is_ignored_but_included_in_tree) {
1316 last_known_is_ignored_but_included_in_tree_value_ =
1317 is_ignored_but_included_in_tree ? kIncludeObject : kIgnoreObject;
1318 }
1319
HasInheritedPresentationalRole() const1320 bool AXObject::HasInheritedPresentationalRole() const {
1321 UpdateCachedAttributeValuesIfNeeded();
1322 return cached_has_inherited_presentational_role_;
1323 }
1324
CanSetValueAttribute() const1325 bool AXObject::CanSetValueAttribute() const {
1326 switch (RoleValue()) {
1327 case ax::mojom::Role::kColorWell:
1328 case ax::mojom::Role::kDate:
1329 case ax::mojom::Role::kDateTime:
1330 case ax::mojom::Role::kScrollBar:
1331 case ax::mojom::Role::kSlider:
1332 case ax::mojom::Role::kSpinButton:
1333 case ax::mojom::Role::kSplitter:
1334 case ax::mojom::Role::kTextField:
1335 case ax::mojom::Role::kTextFieldWithComboBox:
1336 case ax::mojom::Role::kSearchBox:
1337 return Restriction() == kRestrictionNone;
1338 default:
1339 break;
1340 }
1341 return false;
1342 }
1343
1344 // This does not use Element::IsFocusable(), as that can sometimes recalculate
1345 // styles because of IsFocusableStyle() check, resetting the document lifecycle.
CanSetFocusAttribute() const1346 bool AXObject::CanSetFocusAttribute() const {
1347 if (IsDetached())
1348 return false;
1349
1350 // NOT focusable: anything inside a <portal> (the portal element itself is).
1351 if (GetDocument() && GetDocument()->GetPage() &&
1352 GetDocument()->GetPage()->InsidePortal()) {
1353 return false;
1354 }
1355
1356 // Focusable: web area -- this is the only focusable non-element.
1357 if (IsWebArea())
1358 return true;
1359
1360 // NOT focusable: objects with no DOM node, e.g. extra layout blocks inserted
1361 // as filler, or objects where the node is not an element, such as a text
1362 // node or an HTML comment.
1363 Element* elem = GetElement();
1364 if (!elem)
1365 return false;
1366
1367 // NOT focusable: inert elements.
1368 if (elem->IsInert())
1369 return false;
1370
1371 // NOT focusable: disabled form controls.
1372 if (IsDisabledFormControl(elem))
1373 return false;
1374
1375 // Focusable: options in a combobox or listbox.
1376 // Even though they are not treated as supporting focus by Blink (the parent
1377 // widget is), they are considered focusable in the accessibility sense,
1378 // behaving like potential active descendants, and handling focus actions.
1379 // Menu list options are handled before visibility check, because they
1380 // are considered focusable even when part of collapsed drop down.
1381 if (RoleValue() == ax::mojom::Role::kMenuListOption)
1382 return true;
1383
1384 // NOT focusable: hidden elements.
1385 // This is imperfect, because the only way to really know whether something
1386 // is hidden via style is to EnsureComputedStyle(). The method
1387 // AXObject::IsHiddenViaStyle() does this, but it relies on
1388 // EnsureComputedStyle() which could cause instability in callers.
1389 // This code assumes that a canvas descendant has the same visibility as
1390 // the canvas itself.
1391 // TODO(aleventhal) Consider caching visibility when it's safe to compute.
1392 if (!IsA<HTMLAreaElement>(elem)) {
1393 if (!GetLayoutObject()) {
1394 if (!elem->IsInCanvasSubtree())
1395 return false;
1396 const HTMLCanvasElement* canvas =
1397 Traversal<HTMLCanvasElement>::FirstAncestorOrSelf(*elem);
1398 if (!canvas->GetLayoutObject() ||
1399 canvas->GetLayoutObject()->Style()->Visibility() !=
1400 EVisibility::kVisible) {
1401 return false;
1402 }
1403 } else if (GetLayoutObject()->Style()->Visibility() !=
1404 EVisibility::kVisible) {
1405 return false;
1406 }
1407 }
1408
1409 // Focusable: options in a combobox or listbox.
1410 // Similar to menu list option treatment above, but not focusable if hidden.
1411 if (RoleValue() == ax::mojom::Role::kListBoxOption)
1412 return true;
1413
1414 // Focusable: element supports focus.
1415 if (elem->SupportsFocus())
1416 return true;
1417
1418 // TODO(accessibility) Focusable: scrollable with the keyboard.
1419 // Keyboard-focusable scroll containers feature:
1420 // https://www.chromestatus.com/feature/5231964663578624
1421 // When adding here, remove similar check from ::NameFromContents().
1422 // if (RuntimeEnabledFeatures::KeyboardFocusableScrollersEnabled() &&
1423 // IsUserScrollable()) {
1424 // return true;
1425 // }
1426
1427 // Focusable: can be an active descendant.
1428 if (CanBeActiveDescendant())
1429 return true;
1430
1431 // NOT focusable: everything else.
1432 return false;
1433 }
1434
1435 // From ARIA 1.1.
1436 // 1. The value of aria-activedescendant refers to an element that is either a
1437 // descendant of the element with DOM focus or is a logical descendant as
1438 // indicated by the aria-owns attribute. 2. The element with DOM focus is a
1439 // textbox with aria-controls referring to an element that supports
1440 // aria-activedescendant, and the value of aria-activedescendant specified for
1441 // the textbox refers to either a descendant of the element controlled by the
1442 // textbox or is a logical descendant of that controlled element as indicated by
1443 // the aria-owns attribute.
CanBeActiveDescendant() const1444 bool AXObject::CanBeActiveDescendant() const {
1445 // Require an element with an id attribute.
1446 // TODO(accessibility): this code currently requires both an id and role
1447 // attribute, as well as an ancestor or controlling aria-activedescendant.
1448 // However, with element reflection it may be possible to set an active
1449 // descendant without an id, so at some point we may need to remove the
1450 // requirement for an id attribute.
1451 if (!GetElement() || !GetElement()->FastHasAttribute(html_names::kIdAttr))
1452 return false;
1453
1454 // Does not make sense to use aria-activedescendant to point to a
1455 // presentational object.
1456 if (IsPresentational())
1457 return false;
1458
1459 // Does not make sense to use aria-activedescendant to point to an HTML
1460 // element that requires real focus, therefore an ARIA role is necessary.
1461 if (AriaRoleAttribute() == ax::mojom::Role::kUnknown)
1462 return false;
1463
1464 return IsARIAControlledByTextboxWithActiveDescendant() ||
1465 AncestorExposesActiveDescendant();
1466 }
1467
UpdateDistributionForFlatTreeTraversal() const1468 void AXObject::UpdateDistributionForFlatTreeTraversal() const {
1469 Node* node = GetNode();
1470 if (!node) {
1471 AXObject* parent = this->ParentObject();
1472 while (!node && parent) {
1473 node = parent->GetNode();
1474 parent = parent->ParentObject();
1475 }
1476 }
1477
1478 if (node)
1479 node->UpdateDistributionForFlatTreeTraversal();
1480
1481 // TODO(aboxhall): Instead of this, propagate inert down through frames
1482 Document* document = GetDocument();
1483 while (document && document->LocalOwner()) {
1484 document->LocalOwner()->UpdateDistributionForFlatTreeTraversal();
1485 document = document->LocalOwner()->ownerDocument();
1486 }
1487 }
1488
IsARIAControlledByTextboxWithActiveDescendant() const1489 bool AXObject::IsARIAControlledByTextboxWithActiveDescendant() const {
1490 if (IsDetached())
1491 return false;
1492
1493 // This situation should mostly arise when using an active descendant on a
1494 // textbox inside an ARIA 1.1 combo box widget, which points to the selected
1495 // option in a list. In such situations, the active descendant is useful only
1496 // when the textbox is focused. Therefore, we don't currently need to keep
1497 // track of all aria-controls relationships.
1498 const AXObject* focused_object = AXObjectCache().FocusedObject();
1499 if (!focused_object || !focused_object->IsTextControl())
1500 return false;
1501
1502 if (!focused_object->GetAOMPropertyOrARIAAttribute(
1503 AOMRelationProperty::kActiveDescendant)) {
1504 return false;
1505 }
1506
1507 HeapVector<Member<Element>> controlled_by_elements;
1508 if (!focused_object->HasAOMPropertyOrARIAAttribute(
1509 AOMRelationListProperty::kControls, controlled_by_elements)) {
1510 return false;
1511 }
1512
1513 for (const auto& controlled_by_element : controlled_by_elements) {
1514 const AXObject* controlled_by_object =
1515 AXObjectCache().GetOrCreate(controlled_by_element);
1516 if (!controlled_by_object)
1517 continue;
1518
1519 const AXObject* object = this;
1520 while (object && object != controlled_by_object)
1521 object = object->ParentObjectUnignored();
1522 if (object)
1523 return true;
1524 }
1525
1526 return false;
1527 }
1528
AncestorExposesActiveDescendant() const1529 bool AXObject::AncestorExposesActiveDescendant() const {
1530 const AXObject* parent = ParentObjectUnignored();
1531 if (!parent)
1532 return false;
1533
1534 if (parent->GetAOMPropertyOrARIAAttribute(
1535 AOMRelationProperty::kActiveDescendant)) {
1536 return true;
1537 }
1538
1539 return parent->AncestorExposesActiveDescendant();
1540 }
1541
HasIndirectChildren() const1542 bool AXObject::HasIndirectChildren() const {
1543 return RoleValue() == ax::mojom::Role::kTableHeaderContainer;
1544 }
1545
CanSetSelectedAttribute() const1546 bool AXObject::CanSetSelectedAttribute() const {
1547 // Sub-widget elements can be selected if not disabled (native or ARIA)
1548 return IsSubWidget() && Restriction() != kRestrictionDisabled;
1549 }
1550
IsSubWidget() const1551 bool AXObject::IsSubWidget() const {
1552 switch (RoleValue()) {
1553 case ax::mojom::Role::kCell:
1554 case ax::mojom::Role::kColumnHeader:
1555 case ax::mojom::Role::kRowHeader:
1556 case ax::mojom::Role::kColumn:
1557 case ax::mojom::Role::kRow: {
1558 // If it has an explicit ARIA role, it's a subwidget.
1559 //
1560 // Reasoning:
1561 // Static table cells are not selectable, but ARIA grid cells
1562 // and rows definitely are according to the spec. To support
1563 // ARIA 1.0, it's sufficient to just check if there's any
1564 // ARIA role at all, because if so then it must be a grid-related
1565 // role so it must be selectable.
1566 //
1567 // TODO: an ARIA 1.1+ role of "cell", or a role of "row" inside
1568 // an ARIA 1.1 role of "table", should not be selectable. We may
1569 // need to create separate role enums for grid cells vs table cells
1570 // to implement this.
1571 if (AriaRoleAttribute() != ax::mojom::Role::kUnknown)
1572 return true;
1573
1574 // Otherwise it's only a subwidget if it's in a grid or treegrid,
1575 // not in a table.
1576 AXObject* parent = ParentObjectUnignored();
1577 while (parent && !parent->IsTableLikeRole())
1578 parent = parent->ParentObjectUnignored();
1579 if (parent && (parent->RoleValue() == ax::mojom::Role::kGrid ||
1580 parent->RoleValue() == ax::mojom::Role::kTreeGrid))
1581 return true;
1582 return false;
1583 }
1584 case ax::mojom::Role::kListBoxOption:
1585 case ax::mojom::Role::kMenuListOption:
1586 case ax::mojom::Role::kTab:
1587 case ax::mojom::Role::kTreeItem:
1588 return true;
1589 default:
1590 break;
1591 }
1592 return false;
1593 }
1594
SupportsARIASetSizeAndPosInSet() const1595 bool AXObject::SupportsARIASetSizeAndPosInSet() const {
1596 switch (RoleValue()) {
1597 case ax::mojom::Role::kArticle:
1598 case ax::mojom::Role::kComment:
1599 case ax::mojom::Role::kListBoxOption:
1600 case ax::mojom::Role::kListItem:
1601 case ax::mojom::Role::kMenuItem:
1602 case ax::mojom::Role::kMenuItemRadio:
1603 case ax::mojom::Role::kMenuItemCheckBox:
1604 case ax::mojom::Role::kMenuListOption:
1605 case ax::mojom::Role::kRadioButton:
1606 case ax::mojom::Role::kRow:
1607 case ax::mojom::Role::kTab:
1608 case ax::mojom::Role::kTreeItem:
1609 return true;
1610 default:
1611 break;
1612 }
1613
1614 return false;
1615 }
1616
1617 // Simplify whitespace, but preserve a single leading and trailing whitespace
1618 // character if it's present.
1619 // static
CollapseWhitespace(const String & str)1620 String AXObject::CollapseWhitespace(const String& str) {
1621 StringBuilder result;
1622 if (!str.IsEmpty() && IsHTMLSpace<UChar>(str[0]))
1623 result.Append(' ');
1624 result.Append(str.SimplifyWhiteSpace(IsHTMLSpace<UChar>));
1625 if (!str.IsEmpty() && IsHTMLSpace<UChar>(str[str.length() - 1]))
1626 result.Append(' ');
1627 return result.ToString();
1628 }
1629
ComputedName() const1630 String AXObject::ComputedName() const {
1631 ax::mojom::NameFrom name_from;
1632 AXObject::AXObjectVector name_objects;
1633 return GetName(name_from, &name_objects);
1634 }
1635
GetName(ax::mojom::NameFrom & name_from,AXObject::AXObjectVector * name_objects) const1636 String AXObject::GetName(ax::mojom::NameFrom& name_from,
1637 AXObject::AXObjectVector* name_objects) const {
1638 HeapHashSet<Member<const AXObject>> visited;
1639 AXRelatedObjectVector related_objects;
1640 // For purposes of computing a text alternative, if an ignored node is
1641 // included in the tree, assume that it is the target of aria-labelledby or
1642 // aria-describedby, since we can't tell yet whether that's the case. If it
1643 // isn't exposed, the AT will never see the name anyways.
1644 bool hidden_and_ignored_but_included_in_tree =
1645 IsHiddenForTextAlternativeCalculation() &&
1646 AccessibilityIsIgnoredButIncludedInTree();
1647 // Initialize |name_from|, as TextAlternative() might never set it in some
1648 // cases.
1649 name_from = ax::mojom::NameFrom::kNone;
1650 String text = TextAlternative(false, hidden_and_ignored_but_included_in_tree,
1651 visited, name_from, &related_objects, nullptr);
1652
1653 ax::mojom::Role role = RoleValue();
1654 if (!GetNode() ||
1655 (!IsA<HTMLBRElement>(GetNode()) && role != ax::mojom::Role::kStaticText &&
1656 role != ax::mojom::Role::kInlineTextBox))
1657 text = CollapseWhitespace(text);
1658
1659 if (name_objects) {
1660 name_objects->clear();
1661 for (NameSourceRelatedObject* related_object : related_objects)
1662 name_objects->push_back(related_object->object);
1663 }
1664
1665 return text;
1666 }
1667
GetName(NameSources * name_sources) const1668 String AXObject::GetName(NameSources* name_sources) const {
1669 AXObjectSet visited;
1670 ax::mojom::NameFrom tmp_name_from;
1671 AXRelatedObjectVector tmp_related_objects;
1672 // For purposes of computing a text alternative, if an ignored node is
1673 // included in the tree, assume that it is the target of aria-labelledby or
1674 // aria-describedby, since we can't tell yet whether that's the case. If it
1675 // isn't exposed, the AT will never see the name anyways.
1676 bool hidden_and_ignored_but_included_in_tree =
1677 IsHiddenForTextAlternativeCalculation() &&
1678 AccessibilityIsIgnoredButIncludedInTree();
1679 String text =
1680 TextAlternative(false, hidden_and_ignored_but_included_in_tree, visited,
1681 tmp_name_from, &tmp_related_objects, name_sources);
1682 text = text.SimplifyWhiteSpace(IsHTMLSpace<UChar>);
1683 return text;
1684 }
1685
RecursiveTextAlternative(const AXObject & ax_obj,bool in_aria_labelled_by_traversal,AXObjectSet & visited)1686 String AXObject::RecursiveTextAlternative(const AXObject& ax_obj,
1687 bool in_aria_labelled_by_traversal,
1688 AXObjectSet& visited) {
1689 ax::mojom::NameFrom tmp_name_from;
1690 return RecursiveTextAlternative(ax_obj, in_aria_labelled_by_traversal,
1691 visited, tmp_name_from);
1692 }
1693
RecursiveTextAlternative(const AXObject & ax_obj,bool in_aria_labelled_by_traversal,AXObjectSet & visited,ax::mojom::NameFrom & name_from)1694 String AXObject::RecursiveTextAlternative(const AXObject& ax_obj,
1695 bool in_aria_labelled_by_traversal,
1696 AXObjectSet& visited,
1697 ax::mojom::NameFrom& name_from) {
1698 if (visited.Contains(&ax_obj) && !in_aria_labelled_by_traversal)
1699 return String();
1700
1701 return ax_obj.TextAlternative(true, in_aria_labelled_by_traversal, visited,
1702 name_from, nullptr, nullptr);
1703 }
1704
IsHiddenViaStyle() const1705 bool AXObject::IsHiddenViaStyle() const {
1706 if (GetLayoutObject())
1707 return GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible;
1708 if (Node* node = GetNode()) {
1709 if (node->isConnected()) {
1710 bool is_first_loop = true;
1711 auto* element = DynamicTo<Element>(node);
1712 while (element && !element->GetLayoutObject()) {
1713 const ComputedStyle* style = element->EnsureComputedStyle();
1714 if (is_first_loop && style->Visibility() != EVisibility::kVisible)
1715 return true;
1716 // CSS Display:
1717 // - does not inherit
1718 // - display: none affects entire subtrees regardless of descendants
1719 // attempting to override it
1720 // - causes elements to have no associated layout object
1721 // Therefore, check each consecutive parent without a layout object.
1722 if (style->Display() == EDisplay::kNone)
1723 return true;
1724 element = element->parentElement();
1725 is_first_loop = false;
1726 }
1727 }
1728 }
1729 return false;
1730 }
1731
IsHiddenForTextAlternativeCalculation() const1732 bool AXObject::IsHiddenForTextAlternativeCalculation() const {
1733 if (AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty::kHidden))
1734 return false;
1735
1736 if (GetLayoutObject())
1737 return GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible;
1738 else if (GetNode() && IsA<HTMLNoScriptElement>(GetNode()))
1739 return true;
1740
1741 // This is an obscure corner case: if a node has no LayoutObject, that means
1742 // it's not rendered, but we still may be exploring it as part of a text
1743 // alternative calculation, for example if it was explicitly referenced by
1744 // aria-labelledby. So we need to explicitly call the style resolver to check
1745 // whether it's invisible or display:none, rather than relying on the style
1746 // cached in the LayoutObject.
1747 Document* document = GetDocument();
1748 if (!document || !document->GetFrame())
1749 return false;
1750 if (Node* node = GetNode()) {
1751 auto* element = DynamicTo<Element>(node);
1752 if (element && node->isConnected()) {
1753 const ComputedStyle* style = element->EnsureComputedStyle();
1754 return style->Display() == EDisplay::kNone ||
1755 style->Visibility() != EVisibility::kVisible;
1756 }
1757 }
1758 return false;
1759 }
1760
AriaTextAlternative(bool recursive,bool in_aria_labelled_by_traversal,AXObjectSet & visited,ax::mojom::NameFrom & name_from,AXRelatedObjectVector * related_objects,NameSources * name_sources,bool * found_text_alternative) const1761 String AXObject::AriaTextAlternative(bool recursive,
1762 bool in_aria_labelled_by_traversal,
1763 AXObjectSet& visited,
1764 ax::mojom::NameFrom& name_from,
1765 AXRelatedObjectVector* related_objects,
1766 NameSources* name_sources,
1767 bool* found_text_alternative) const {
1768 String text_alternative;
1769 bool already_visited = visited.Contains(this);
1770 visited.insert(this);
1771
1772 // Step 2A from: http://www.w3.org/TR/accname-aam-1.1
1773 // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
1774 if (!in_aria_labelled_by_traversal &&
1775 IsHiddenForTextAlternativeCalculation()) {
1776 *found_text_alternative = true;
1777 return String();
1778 }
1779
1780 // Step 2B from: http://www.w3.org/TR/accname-aam-1.1
1781 // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
1782 if (!in_aria_labelled_by_traversal && !already_visited) {
1783 name_from = ax::mojom::NameFrom::kRelatedElement;
1784
1785 // Check ARIA attributes.
1786 const QualifiedName& attr =
1787 HasAttribute(html_names::kAriaLabeledbyAttr) &&
1788 !HasAttribute(html_names::kAriaLabelledbyAttr)
1789 ? html_names::kAriaLabeledbyAttr
1790 : html_names::kAriaLabelledbyAttr;
1791
1792 if (name_sources) {
1793 name_sources->push_back(NameSource(*found_text_alternative, attr));
1794 name_sources->back().type = name_from;
1795 }
1796
1797 Element* element = GetElement();
1798 if (element) {
1799 HeapVector<Member<Element>> elements_from_attribute;
1800 Vector<String> ids;
1801 ElementsFromAttribute(elements_from_attribute, attr, ids);
1802
1803 const AtomicString& aria_labelledby = GetAttribute(attr);
1804
1805 if (!aria_labelledby.IsNull()) {
1806 if (name_sources)
1807 name_sources->back().attribute_value = aria_labelledby;
1808
1809 // Operate on a copy of |visited| so that if |name_sources| is not
1810 // null, the set of visited objects is preserved unmodified for future
1811 // calculations.
1812 AXObjectSet visited_copy = visited;
1813 text_alternative = TextFromElements(
1814 true, visited, elements_from_attribute, related_objects);
1815 if (!ids.IsEmpty())
1816 AXObjectCache().UpdateReverseRelations(this, ids);
1817 if (!text_alternative.IsNull()) {
1818 if (name_sources) {
1819 NameSource& source = name_sources->back();
1820 source.type = name_from;
1821 source.related_objects = *related_objects;
1822 source.text = text_alternative;
1823 *found_text_alternative = true;
1824 } else {
1825 *found_text_alternative = true;
1826 return text_alternative;
1827 }
1828 } else if (name_sources) {
1829 name_sources->back().invalid = true;
1830 }
1831 }
1832 }
1833 }
1834
1835 // Step 2C from: http://www.w3.org/TR/accname-aam-1.1
1836 // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
1837 name_from = ax::mojom::NameFrom::kAttribute;
1838 if (name_sources) {
1839 name_sources->push_back(
1840 NameSource(*found_text_alternative, html_names::kAriaLabelAttr));
1841 name_sources->back().type = name_from;
1842 }
1843 const AtomicString& aria_label =
1844 GetAOMPropertyOrARIAAttribute(AOMStringProperty::kLabel);
1845 if (!aria_label.IsEmpty()) {
1846 text_alternative = aria_label;
1847
1848 if (name_sources) {
1849 NameSource& source = name_sources->back();
1850 source.text = text_alternative;
1851 source.attribute_value = aria_label;
1852 *found_text_alternative = true;
1853 } else {
1854 *found_text_alternative = true;
1855 return text_alternative;
1856 }
1857 }
1858
1859 return text_alternative;
1860 }
1861
TextFromElements(bool in_aria_labelledby_traversal,AXObjectSet & visited,HeapVector<Member<Element>> & elements,AXRelatedObjectVector * related_objects) const1862 String AXObject::TextFromElements(
1863 bool in_aria_labelledby_traversal,
1864 AXObjectSet& visited,
1865 HeapVector<Member<Element>>& elements,
1866 AXRelatedObjectVector* related_objects) const {
1867 StringBuilder accumulated_text;
1868 bool found_valid_element = false;
1869 AXRelatedObjectVector local_related_objects;
1870
1871 for (const auto& element : elements) {
1872 AXObject* ax_element = AXObjectCache().GetOrCreate(element);
1873 if (ax_element) {
1874 found_valid_element = true;
1875
1876 String result = RecursiveTextAlternative(
1877 *ax_element, in_aria_labelledby_traversal, visited);
1878 visited.insert(ax_element);
1879 local_related_objects.push_back(
1880 MakeGarbageCollected<NameSourceRelatedObject>(ax_element, result));
1881 if (!result.IsEmpty()) {
1882 if (!accumulated_text.IsEmpty())
1883 accumulated_text.Append(' ');
1884 accumulated_text.Append(result);
1885 }
1886 }
1887 }
1888 if (!found_valid_element)
1889 return String();
1890 if (related_objects)
1891 *related_objects = local_related_objects;
1892 return accumulated_text.ToString();
1893 }
1894
TokenVectorFromAttribute(Vector<String> & tokens,const QualifiedName & attribute) const1895 void AXObject::TokenVectorFromAttribute(Vector<String>& tokens,
1896 const QualifiedName& attribute) const {
1897 Node* node = this->GetNode();
1898 if (!node || !node->IsElementNode())
1899 return;
1900
1901 String attribute_value = GetAttribute(attribute).GetString();
1902 if (attribute_value.IsEmpty())
1903 return;
1904
1905 attribute_value = attribute_value.SimplifyWhiteSpace();
1906 attribute_value.Split(' ', tokens);
1907 }
1908
ElementsFromAttribute(HeapVector<Member<Element>> & elements,const QualifiedName & attribute,Vector<String> & ids) const1909 void AXObject::ElementsFromAttribute(HeapVector<Member<Element>>& elements,
1910 const QualifiedName& attribute,
1911 Vector<String>& ids) const {
1912 // We compute the attr-associated elements, which are either explicitly set
1913 // element references set via the IDL, or computed from the content attribute.
1914 TokenVectorFromAttribute(ids, attribute);
1915 Element* element = GetElement();
1916 if (!element)
1917 return;
1918
1919 bool attr_associated_elements_are_null = true;
1920 HeapVector<Member<Element>> attr_associated_elements =
1921 element->GetElementArrayAttribute(attribute,
1922 attr_associated_elements_are_null);
1923 if (attr_associated_elements_are_null)
1924 return;
1925
1926 for (const auto& element : attr_associated_elements)
1927 elements.push_back(element);
1928 }
1929
AriaLabelledbyElementVector(HeapVector<Member<Element>> & elements,Vector<String> & ids) const1930 void AXObject::AriaLabelledbyElementVector(
1931 HeapVector<Member<Element>>& elements,
1932 Vector<String>& ids) const {
1933 // Try both spellings, but prefer aria-labelledby, which is the official spec.
1934 ElementsFromAttribute(elements, html_names::kAriaLabelledbyAttr, ids);
1935 if (!ids.size())
1936 ElementsFromAttribute(elements, html_names::kAriaLabeledbyAttr, ids);
1937 }
1938
TextFromAriaLabelledby(AXObjectSet & visited,AXRelatedObjectVector * related_objects,Vector<String> & ids) const1939 String AXObject::TextFromAriaLabelledby(AXObjectSet& visited,
1940 AXRelatedObjectVector* related_objects,
1941 Vector<String>& ids) const {
1942 HeapVector<Member<Element>> elements;
1943 AriaLabelledbyElementVector(elements, ids);
1944 return TextFromElements(true, visited, elements, related_objects);
1945 }
1946
TextFromAriaDescribedby(AXRelatedObjectVector * related_objects,Vector<String> & ids) const1947 String AXObject::TextFromAriaDescribedby(AXRelatedObjectVector* related_objects,
1948 Vector<String>& ids) const {
1949 AXObjectSet visited;
1950 HeapVector<Member<Element>> elements;
1951 ElementsFromAttribute(elements, html_names::kAriaDescribedbyAttr, ids);
1952 return TextFromElements(true, visited, elements, related_objects);
1953 }
1954
BackgroundColor() const1955 RGBA32 AXObject::BackgroundColor() const {
1956 UpdateCachedAttributeValuesIfNeeded();
1957 return cached_background_color_;
1958 }
1959
Orientation() const1960 AccessibilityOrientation AXObject::Orientation() const {
1961 // In ARIA 1.1, the default value for aria-orientation changed from
1962 // horizontal to undefined.
1963 return kAccessibilityOrientationUndefined;
1964 }
1965
Markers(Vector<DocumentMarker::MarkerType> &,Vector<AXRange> &) const1966 void AXObject::Markers(Vector<DocumentMarker::MarkerType>&,
1967 Vector<AXRange>&) const {}
1968
TextCharacterOffsets(Vector<int> &) const1969 void AXObject::TextCharacterOffsets(Vector<int>&) const {}
1970
GetWordBoundaries(Vector<int> & word_starts,Vector<int> & word_ends) const1971 void AXObject::GetWordBoundaries(Vector<int>& word_starts,
1972 Vector<int>& word_ends) const {}
1973
Action() const1974 ax::mojom::DefaultActionVerb AXObject::Action() const {
1975 Element* action_element = ActionElement();
1976 if (!action_element)
1977 return ax::mojom::DefaultActionVerb::kNone;
1978
1979 // TODO(dmazzoni): Ensure that combo box text field is handled here.
1980 if (IsTextControl())
1981 return ax::mojom::DefaultActionVerb::kActivate;
1982
1983 if (IsCheckable()) {
1984 return CheckedState() != ax::mojom::CheckedState::kTrue
1985 ? ax::mojom::DefaultActionVerb::kCheck
1986 : ax::mojom::DefaultActionVerb::kUncheck;
1987 }
1988
1989 // If this object cannot receive focus and has a button role, use click as
1990 // the default action. On the AuraLinux platform, the press action is a
1991 // signal to users that they can trigger the action using the keyboard, while
1992 // a click action means the user should trigger the action via a simulated
1993 // click. If this object cannot receive focus, it's impossible to trigger it
1994 // with a key press.
1995 if (RoleValue() == ax::mojom::Role::kButton && !CanSetFocusAttribute())
1996 return ax::mojom::DefaultActionVerb::kClick;
1997
1998 switch (RoleValue()) {
1999 case ax::mojom::Role::kButton:
2000 case ax::mojom::Role::kDisclosureTriangle:
2001 case ax::mojom::Role::kToggleButton:
2002 return ax::mojom::DefaultActionVerb::kPress;
2003 case ax::mojom::Role::kListBoxOption:
2004 case ax::mojom::Role::kMenuItemRadio:
2005 case ax::mojom::Role::kMenuItem:
2006 case ax::mojom::Role::kMenuListOption:
2007 return ax::mojom::DefaultActionVerb::kSelect;
2008 case ax::mojom::Role::kLink:
2009 return ax::mojom::DefaultActionVerb::kJump;
2010 case ax::mojom::Role::kComboBoxMenuButton:
2011 case ax::mojom::Role::kPopUpButton:
2012 return ax::mojom::DefaultActionVerb::kOpen;
2013 default:
2014 if (action_element == GetNode())
2015 return ax::mojom::DefaultActionVerb::kClick;
2016 return ax::mojom::DefaultActionVerb::kClickAncestor;
2017 }
2018 }
2019
AriaPressedIsPresent() const2020 bool AXObject::AriaPressedIsPresent() const {
2021 AtomicString result;
2022 return HasAOMPropertyOrARIAAttribute(AOMStringProperty::kPressed, result);
2023 }
2024
AriaCheckedIsPresent() const2025 bool AXObject::AriaCheckedIsPresent() const {
2026 AtomicString result;
2027 return HasAOMPropertyOrARIAAttribute(AOMStringProperty::kChecked, result);
2028 }
2029
SupportsARIAExpanded() const2030 bool AXObject::SupportsARIAExpanded() const {
2031 switch (RoleValue()) {
2032 case ax::mojom::Role::kApplication:
2033 case ax::mojom::Role::kButton:
2034 case ax::mojom::Role::kCheckBox:
2035 case ax::mojom::Role::kColumnHeader:
2036 case ax::mojom::Role::kComboBoxGrouping:
2037 case ax::mojom::Role::kComboBoxMenuButton:
2038 case ax::mojom::Role::kDisclosureTriangle:
2039 case ax::mojom::Role::kListBox:
2040 case ax::mojom::Role::kLink:
2041 case ax::mojom::Role::kPopUpButton:
2042 case ax::mojom::Role::kMenuButton:
2043 case ax::mojom::Role::kMenuItem:
2044 case ax::mojom::Role::kMenuItemCheckBox:
2045 case ax::mojom::Role::kMenuItemRadio:
2046 case ax::mojom::Role::kRow:
2047 case ax::mojom::Role::kRowHeader:
2048 case ax::mojom::Role::kSwitch:
2049 case ax::mojom::Role::kTab:
2050 case ax::mojom::Role::kTextFieldWithComboBox:
2051 case ax::mojom::Role::kTreeItem:
2052 return true;
2053 case ax::mojom::Role::kCell:
2054 // TODO(Accessibility): aria-expanded is supported on grid cells but not
2055 // on cells inside a static table. Consider creating separate internal
2056 // roles so that we can easily distinguish these two types. See also
2057 // IsSubWidget().
2058 return true;
2059 default:
2060 return false;
2061 }
2062 }
2063
IsGlobalARIAAttribute(const AtomicString & name)2064 bool IsGlobalARIAAttribute(const AtomicString& name) {
2065 if (!name.StartsWith("ARIA"))
2066 return false;
2067 if (name.StartsWith("ARIA-ATOMIC"))
2068 return true;
2069 if (name.StartsWith("ARIA-BUSY"))
2070 return true;
2071 if (name.StartsWith("ARIA-CONTROLS"))
2072 return true;
2073 if (name.StartsWith("ARIA-CURRENT"))
2074 return true;
2075 if (name.StartsWith("ARIA-DESCRIBEDBY"))
2076 return true;
2077 if (name.StartsWith("ARIA-DETAILS"))
2078 return true;
2079 if (name.StartsWith("ARIA-DISABLED"))
2080 return true;
2081 if (name.StartsWith("ARIA-DROPEFFECT"))
2082 return true;
2083 if (name.StartsWith("ARIA-ERRORMESSAGE"))
2084 return true;
2085 if (name.StartsWith("ARIA-FLOWTO"))
2086 return true;
2087 if (name.StartsWith("ARIA-GRABBED"))
2088 return true;
2089 if (name.StartsWith("ARIA-HASPOPUP"))
2090 return true;
2091 if (name.StartsWith("ARIA-HIDDEN"))
2092 return true;
2093 if (name.StartsWith("ARIA-INVALID"))
2094 return true;
2095 if (name.StartsWith("ARIA-KEYSHORTCUTS"))
2096 return true;
2097 if (name.StartsWith("ARIA-LABEL"))
2098 return true;
2099 if (name.StartsWith("ARIA-LABELEDBY"))
2100 return true;
2101 if (name.StartsWith("ARIA-LABELLEDBY"))
2102 return true;
2103 if (name.StartsWith("ARIA-LIVE"))
2104 return true;
2105 if (name.StartsWith("ARIA-OWNS"))
2106 return true;
2107 if (name.StartsWith("ARIA-RELEVANT"))
2108 return true;
2109 if (name.StartsWith("ARIA-ROLEDESCRIPTION"))
2110 return true;
2111 return false;
2112 }
2113
HasGlobalARIAAttribute() const2114 bool AXObject::HasGlobalARIAAttribute() const {
2115 auto* element = GetElement();
2116 if (!element)
2117 return false;
2118
2119 AttributeCollection attributes = element->AttributesWithoutUpdate();
2120 for (const Attribute& attr : attributes) {
2121 // Attributes cache their uppercase names.
2122 auto name = attr.GetName().LocalNameUpper();
2123 if (IsGlobalARIAAttribute(name))
2124 return true;
2125 }
2126 if (!element->DidAttachInternals())
2127 return false;
2128 const auto& internals_attributes =
2129 element->EnsureElementInternals().GetAttributes();
2130 for (const QualifiedName& attr : internals_attributes.Keys()) {
2131 if (IsGlobalARIAAttribute(attr.LocalNameUpper()))
2132 return true;
2133 }
2134 return false;
2135 }
2136
SupportsRangeValue() const2137 bool AXObject::SupportsRangeValue() const {
2138 return IsProgressIndicator() || IsMeter() || IsSlider() || IsScrollbar() ||
2139 IsSpinButton() || IsMoveableSplitter();
2140 }
2141
IndexInParent() const2142 int AXObject::IndexInParent() const {
2143 DCHECK(AccessibilityIsIncludedInTree())
2144 << "IndexInParent is only valid when a node is included in the tree";
2145 if (!ParentObjectIncludedInTree())
2146 return 0;
2147
2148 const AXObjectVector& siblings = ParentObjectIncludedInTree()->Children();
2149 wtf_size_t index = siblings.Find(this);
2150 DCHECK(index != kNotFound);
2151 return (index == kNotFound) ? 0 : static_cast<int>(index);
2152 }
2153
IsLiveRegionRoot() const2154 bool AXObject::IsLiveRegionRoot() const {
2155 const AtomicString& live_region = LiveRegionStatus();
2156 return !live_region.IsEmpty();
2157 }
2158
IsActiveLiveRegionRoot() const2159 bool AXObject::IsActiveLiveRegionRoot() const {
2160 const AtomicString& live_region = LiveRegionStatus();
2161 return !live_region.IsEmpty() && !EqualIgnoringASCIICase(live_region, "off");
2162 }
2163
Restriction() const2164 AXRestriction AXObject::Restriction() const {
2165 // According to ARIA, all elements of the base markup can be disabled.
2166 // According to CORE-AAM, any focusable descendant of aria-disabled
2167 // ancestor is also disabled.
2168 bool is_disabled;
2169 if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kDisabled,
2170 is_disabled)) {
2171 // Has aria-disabled, overrides native markup determining disabled.
2172 if (is_disabled)
2173 return kRestrictionDisabled;
2174 } else if (CanSetFocusAttribute() && IsDescendantOfDisabledNode()) {
2175 // aria-disabled on an ancestor propagates to focusable descendants.
2176 return kRestrictionDisabled;
2177 }
2178
2179 // Check aria-readonly if supported by current role.
2180 bool is_read_only;
2181 if (SupportsARIAReadOnly() &&
2182 HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kReadOnly,
2183 is_read_only)) {
2184 // ARIA overrides other readonly state markup.
2185 return is_read_only ? kRestrictionReadOnly : kRestrictionNone;
2186 }
2187
2188 // This is a node that is not readonly and not disabled.
2189 return kRestrictionNone;
2190 }
2191
DetermineAccessibilityRole()2192 ax::mojom::Role AXObject::DetermineAccessibilityRole() {
2193 aria_role_ = DetermineAriaRoleAttribute();
2194 return aria_role_;
2195 }
2196
AriaRoleAttribute() const2197 ax::mojom::Role AXObject::AriaRoleAttribute() const {
2198 return aria_role_;
2199 }
2200
DetermineAriaRoleAttribute() const2201 ax::mojom::Role AXObject::DetermineAriaRoleAttribute() const {
2202 const AtomicString& aria_role =
2203 GetAOMPropertyOrARIAAttribute(AOMStringProperty::kRole);
2204 if (aria_role.IsNull() || aria_role.IsEmpty())
2205 return ax::mojom::Role::kUnknown;
2206
2207 ax::mojom::Role role = AriaRoleToWebCoreRole(aria_role);
2208
2209 switch (role) {
2210 case ax::mojom::Role::kComment:
2211 case ax::mojom::Role::kMark:
2212 case ax::mojom::Role::kSuggestion:
2213 UseCounter::Count(GetDocument(), WebFeature::kARIAAnnotations);
2214 if (!RuntimeEnabledFeatures::AccessibilityExposeARIAAnnotationsEnabled(
2215 GetDocument())) {
2216 role = ax::mojom::Role::kGenericContainer;
2217 }
2218 break;
2219 default:
2220 break;
2221 }
2222
2223 // ARIA states if an item can get focus, it should not be presentational.
2224 // It also states user agents should ignore the presentational role if
2225 // the element has global ARIA states and properties.
2226 if ((role == ax::mojom::Role::kNone ||
2227 role == ax::mojom::Role::kPresentational) &&
2228 (CanSetFocusAttribute() || HasGlobalARIAAttribute()))
2229 return ax::mojom::Role::kUnknown;
2230
2231 if (role == ax::mojom::Role::kButton)
2232 role = ButtonRoleType();
2233
2234 role = RemapAriaRoleDueToParent(role);
2235
2236 // Distinguish between different uses of the "combobox" role:
2237 //
2238 // ax::mojom::Role::kComboBoxGrouping:
2239 // <div role="combobox"><input></div>
2240 // ax::mojom::Role::kTextFieldWithComboBox:
2241 // <input role="combobox">
2242 // ax::mojom::Role::kComboBoxMenuButton:
2243 // <div tabindex=0 role="combobox">Select</div>
2244 if (role == ax::mojom::Role::kComboBoxGrouping) {
2245 if (IsNativeTextControl())
2246 role = ax::mojom::Role::kTextFieldWithComboBox;
2247 else if (GetElement() && GetElement()->SupportsFocus())
2248 role = ax::mojom::Role::kComboBoxMenuButton;
2249 }
2250
2251 if (role != ax::mojom::Role::kUnknown)
2252 return role;
2253
2254 return ax::mojom::Role::kUnknown;
2255 }
2256
RemapAriaRoleDueToParent(ax::mojom::Role role) const2257 ax::mojom::Role AXObject::RemapAriaRoleDueToParent(ax::mojom::Role role) const {
2258 // Some objects change their role based on their parent.
2259 // However, asking for the unignoredParent calls accessibilityIsIgnored(),
2260 // which can trigger a loop. While inside the call stack of creating an
2261 // element, we need to avoid accessibilityIsIgnored().
2262 // https://bugs.webkit.org/show_bug.cgi?id=65174
2263
2264 // Don't return table roles unless inside a table-like container.
2265 switch (role) {
2266 case ax::mojom::Role::kRow:
2267 case ax::mojom::Role::kRowGroup:
2268 case ax::mojom::Role::kCell:
2269 case ax::mojom::Role::kRowHeader:
2270 case ax::mojom::Role::kColumnHeader:
2271 for (AXObject* ancestor = ParentObjectUnignored(); ancestor;
2272 ancestor = ancestor->ParentObjectUnignored()) {
2273 ax::mojom::Role ancestor_aria_role = ancestor->AriaRoleAttribute();
2274 if (ancestor_aria_role == ax::mojom::Role::kCell)
2275 return ax::mojom::Role::kGenericContainer; // In another cell,
2276 // illegal.
2277 if (ancestor->IsTableLikeRole())
2278 return role; // Inside a table: ARIA role is legal.
2279 }
2280 return ax::mojom::Role::kGenericContainer; // Not in a table.
2281 default:
2282 break;
2283 }
2284
2285 if (role != ax::mojom::Role::kListBoxOption &&
2286 role != ax::mojom::Role::kMenuItem)
2287 return role;
2288
2289 for (AXObject* parent = ParentObject();
2290 parent && !parent->AccessibilityIsIgnored();
2291 parent = parent->ParentObject()) {
2292 ax::mojom::Role parent_aria_role = parent->AriaRoleAttribute();
2293
2294 // Selects and listboxes both have options as child roles, but they map to
2295 // different roles within WebCore.
2296 if (role == ax::mojom::Role::kListBoxOption &&
2297 parent_aria_role == ax::mojom::Role::kMenu)
2298 return ax::mojom::Role::kMenuItem;
2299 // An aria "menuitem" may map to MenuButton or MenuItem depending on its
2300 // parent.
2301 if (role == ax::mojom::Role::kMenuItem &&
2302 parent_aria_role == ax::mojom::Role::kGroup)
2303 return ax::mojom::Role::kMenuButton;
2304
2305 // If the parent had a different role, then we don't need to continue
2306 // searching up the chain.
2307 if (parent_aria_role != ax::mojom::Role::kUnknown)
2308 break;
2309 }
2310
2311 return role;
2312 }
2313
IsEditableRoot() const2314 bool AXObject::IsEditableRoot() const {
2315 UpdateCachedAttributeValuesIfNeeded();
2316 return cached_is_editable_root_;
2317 }
2318
LiveRegionRoot() const2319 AXObject* AXObject::LiveRegionRoot() const {
2320 UpdateCachedAttributeValuesIfNeeded();
2321 return cached_live_region_root_;
2322 }
2323
LiveRegionAtomic() const2324 bool AXObject::LiveRegionAtomic() const {
2325 bool atomic = false;
2326 if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kAtomic, atomic))
2327 return atomic;
2328
2329 // ARIA roles "alert" and "status" should have an implicit aria-atomic value
2330 // of true.
2331 return RoleValue() == ax::mojom::Role::kAlert ||
2332 RoleValue() == ax::mojom::Role::kStatus;
2333 }
2334
ContainerLiveRegionStatus() const2335 const AtomicString& AXObject::ContainerLiveRegionStatus() const {
2336 UpdateCachedAttributeValuesIfNeeded();
2337 return cached_live_region_root_ ? cached_live_region_root_->LiveRegionStatus()
2338 : g_null_atom;
2339 }
2340
ContainerLiveRegionRelevant() const2341 const AtomicString& AXObject::ContainerLiveRegionRelevant() const {
2342 UpdateCachedAttributeValuesIfNeeded();
2343 return cached_live_region_root_
2344 ? cached_live_region_root_->LiveRegionRelevant()
2345 : g_null_atom;
2346 }
2347
ContainerLiveRegionAtomic() const2348 bool AXObject::ContainerLiveRegionAtomic() const {
2349 UpdateCachedAttributeValuesIfNeeded();
2350 return cached_live_region_root_ &&
2351 cached_live_region_root_->LiveRegionAtomic();
2352 }
2353
ContainerLiveRegionBusy() const2354 bool AXObject::ContainerLiveRegionBusy() const {
2355 UpdateCachedAttributeValuesIfNeeded();
2356 return cached_live_region_root_ &&
2357 cached_live_region_root_->AOMPropertyOrARIAAttributeIsTrue(
2358 AOMBooleanProperty::kBusy);
2359 }
2360
ElementAccessibilityHitTest(const IntPoint & point) const2361 AXObject* AXObject::ElementAccessibilityHitTest(const IntPoint& point) const {
2362 // Check if there are any mock elements that need to be handled.
2363 for (const auto& child : children_) {
2364 if (child->IsMockObject() &&
2365 child->GetBoundsInFrameCoordinates().Contains(point))
2366 return child->ElementAccessibilityHitTest(point);
2367 }
2368
2369 return const_cast<AXObject*>(this);
2370 }
2371
AncestorsBegin()2372 AXObject::AncestorsIterator AXObject::AncestorsBegin() {
2373 AXObject* parent = ParentObjectUnignored();
2374 if (parent)
2375 return AXObject::AncestorsIterator(*parent);
2376 return AncestorsEnd();
2377 }
2378
AncestorsEnd()2379 AXObject::AncestorsIterator AXObject::AncestorsEnd() {
2380 return AXObject::AncestorsIterator();
2381 }
2382
GetInOrderTraversalIterator()2383 AXObject::InOrderTraversalIterator AXObject::GetInOrderTraversalIterator() {
2384 return InOrderTraversalIterator(*this);
2385 }
2386
ChildCount() const2387 int AXObject::ChildCount() const {
2388 return HasIndirectChildren() ? 0 : static_cast<int>(Children().size());
2389 }
2390
Children() const2391 const AXObject::AXObjectVector& AXObject::Children() const {
2392 return const_cast<AXObject*>(this)->Children();
2393 }
2394
Children()2395 const AXObject::AXObjectVector& AXObject::Children() {
2396 UpdateChildrenIfNecessary();
2397
2398 return children_;
2399 }
2400
FirstChild() const2401 AXObject* AXObject::FirstChild() const {
2402 return ChildCount() ? *Children().begin() : nullptr;
2403 }
2404
LastChild() const2405 AXObject* AXObject::LastChild() const {
2406 return ChildCount() ? *(Children().end() - 1) : nullptr;
2407 }
2408
DeepestFirstChild() const2409 AXObject* AXObject::DeepestFirstChild() const {
2410 if (!ChildCount())
2411 return nullptr;
2412
2413 AXObject* deepest_child = FirstChild();
2414 while (deepest_child->ChildCount())
2415 deepest_child = deepest_child->FirstChild();
2416
2417 return deepest_child;
2418 }
2419
DeepestLastChild() const2420 AXObject* AXObject::DeepestLastChild() const {
2421 if (!ChildCount())
2422 return nullptr;
2423
2424 AXObject* deepest_child = LastChild();
2425 while (deepest_child->ChildCount())
2426 deepest_child = deepest_child->LastChild();
2427
2428 return deepest_child;
2429 }
2430
IsAncestorOf(const AXObject & descendant) const2431 bool AXObject::IsAncestorOf(const AXObject& descendant) const {
2432 return descendant.IsDescendantOf(*this);
2433 }
2434
IsDescendantOf(const AXObject & ancestor) const2435 bool AXObject::IsDescendantOf(const AXObject& ancestor) const {
2436 const AXObject* parent = ParentObject();
2437 while (parent && parent != &ancestor)
2438 parent = parent->ParentObject();
2439 return !!parent;
2440 }
2441
NextSiblingIncludingIgnored() const2442 AXObject* AXObject::NextSiblingIncludingIgnored() const {
2443 if (!AccessibilityIsIncludedInTree()) {
2444 NOTREACHED() << "We don't support iterating over objects excluded "
2445 "from the accessibility tree";
2446 return nullptr;
2447 }
2448
2449 const AXObject* parent_in_tree = ParentObjectIncludedInTree();
2450 if (!parent_in_tree)
2451 return nullptr;
2452
2453 const int index_in_parent = IndexInParent();
2454 if (index_in_parent < parent_in_tree->ChildCount() - 1)
2455 return *(parent_in_tree->Children().begin() + index_in_parent + 1);
2456 return nullptr;
2457 }
2458
PreviousSiblingIncludingIgnored() const2459 AXObject* AXObject::PreviousSiblingIncludingIgnored() const {
2460 if (!AccessibilityIsIncludedInTree()) {
2461 NOTREACHED() << "We don't support iterating over objects excluded "
2462 "from the accessibility tree";
2463 return nullptr;
2464 }
2465
2466 const AXObject* parent_in_tree = ParentObjectIncludedInTree();
2467 if (!parent_in_tree)
2468 return nullptr;
2469
2470 const int index_in_parent = IndexInParent();
2471 if (index_in_parent > 0)
2472 return *(parent_in_tree->Children().begin() + index_in_parent - 1);
2473 return nullptr;
2474 }
2475
NextInPreOrderIncludingIgnored(const AXObject * within) const2476 AXObject* AXObject::NextInPreOrderIncludingIgnored(
2477 const AXObject* within) const {
2478 if (!AccessibilityIsIncludedInTree()) {
2479 NOTREACHED() << "We don't support iterating over objects excluded "
2480 "from the accessibility tree";
2481 return nullptr;
2482 }
2483
2484 if (ChildCount())
2485 return FirstChild();
2486
2487 if (within == this)
2488 return nullptr;
2489
2490 const AXObject* current = this;
2491 AXObject* next = current->NextSiblingIncludingIgnored();
2492 for (; !next; next = current->NextSiblingIncludingIgnored()) {
2493 current = current->ParentObjectIncludedInTree();
2494 if (!current || within == current)
2495 return nullptr;
2496 }
2497 return next;
2498 }
2499
PreviousInPreOrderIncludingIgnored(const AXObject * within) const2500 AXObject* AXObject::PreviousInPreOrderIncludingIgnored(
2501 const AXObject* within) const {
2502 if (!AccessibilityIsIncludedInTree()) {
2503 NOTREACHED() << "We don't support iterating over objects excluded "
2504 "from the accessibility tree";
2505 return nullptr;
2506 }
2507 if (within == this)
2508 return nullptr;
2509
2510 if (AXObject* sibling = PreviousSiblingIncludingIgnored()) {
2511 if (sibling->ChildCount())
2512 return sibling->DeepestLastChild();
2513 return sibling;
2514 }
2515
2516 return ParentObjectIncludedInTree();
2517 }
2518
PreviousInPostOrderIncludingIgnored(const AXObject * within) const2519 AXObject* AXObject::PreviousInPostOrderIncludingIgnored(
2520 const AXObject* within) const {
2521 if (!AccessibilityIsIncludedInTree()) {
2522 NOTREACHED() << "We don't support iterating over objects excluded "
2523 "from the accessibility tree";
2524 return nullptr;
2525 }
2526
2527 if (ChildCount())
2528 return LastChild();
2529
2530 if (within == this)
2531 return nullptr;
2532
2533 const AXObject* current = this;
2534 AXObject* previous = current->PreviousSiblingIncludingIgnored();
2535 for (; !previous; previous = current->PreviousSiblingIncludingIgnored()) {
2536 current = current->ParentObjectIncludedInTree();
2537 if (!current || within == current)
2538 return nullptr;
2539 }
2540 return previous;
2541 }
2542
NextSibling() const2543 AXObject* AXObject::NextSibling() const {
2544 if (AccessibilityIsIgnored()) {
2545 NOTREACHED() << "We don't support finding siblings for ignored objects.";
2546 return nullptr;
2547 }
2548
2549 // Find the next sibling for the same unignored parent object,
2550 // flattening accessibility ignored objects.
2551 //
2552 // For example :
2553 // ++A
2554 // ++++B
2555 // ++++C IGNORED
2556 // ++++++E
2557 // ++++D
2558 // Objects [B, E, D] will be siblings since C is ignored.
2559
2560 const AXObject* unignored_parent = ParentObjectUnignored();
2561 const AXObject* current_obj = this;
2562 while (current_obj) {
2563 AXObject* sibling = current_obj->NextSiblingIncludingIgnored();
2564 if (sibling) {
2565 // If we found an ignored sibling, walk in next pre-order
2566 // until an unignored object is found, flattening the ignored object.
2567 while (sibling && sibling->AccessibilityIsIgnored()) {
2568 sibling = sibling->NextInPreOrderIncludingIgnored(unignored_parent);
2569 }
2570 return sibling;
2571 }
2572
2573 // If a sibling has not been found, try again with the parent object,
2574 // until the unignored parent is reached.
2575 current_obj = current_obj->ParentObjectIncludedInTree();
2576 if (!current_obj || !current_obj->AccessibilityIsIgnored())
2577 return nullptr;
2578 }
2579 return nullptr;
2580 }
2581
PreviousSibling() const2582 AXObject* AXObject::PreviousSibling() const {
2583 if (AccessibilityIsIgnored()) {
2584 NOTREACHED() << "We don't support finding siblings for ignored objects.";
2585 return nullptr;
2586 }
2587
2588 // Find the previous sibling for the same unignored parent object,
2589 // flattening accessibility ignored objects.
2590 //
2591 // For example :
2592 // ++A
2593 // ++++B
2594 // ++++C IGNORED
2595 // ++++++E
2596 // ++++D
2597 // Objects [B, E, D] will be siblings since C is ignored.
2598
2599 const AXObject* current_obj = this;
2600 while (current_obj) {
2601 AXObject* sibling = current_obj->PreviousSiblingIncludingIgnored();
2602 if (sibling) {
2603 const AXObject* unignored_parent = ParentObjectUnignored();
2604 // If we found an ignored sibling, walk in previous post-order
2605 // until an unignored object is found, flattening the ignored object.
2606 while (sibling && sibling->AccessibilityIsIgnored()) {
2607 sibling =
2608 sibling->PreviousInPostOrderIncludingIgnored(unignored_parent);
2609 }
2610 return sibling;
2611 }
2612
2613 // If a sibling has not been found, try again with the parent object,
2614 // until the unignored parent is reached.
2615 current_obj = current_obj->ParentObjectIncludedInTree();
2616 if (!current_obj || !current_obj->AccessibilityIsIgnored())
2617 return nullptr;
2618 }
2619 return nullptr;
2620 }
2621
NextInTreeObject() const2622 AXObject* AXObject::NextInTreeObject() const {
2623 AXObject* next = NextInPreOrderIncludingIgnored();
2624 while (next && next->AccessibilityIsIgnored()) {
2625 next = next->NextInPreOrderIncludingIgnored();
2626 }
2627 return next;
2628 }
2629
PreviousInTreeObject() const2630 AXObject* AXObject::PreviousInTreeObject() const {
2631 AXObject* previous = PreviousInPreOrderIncludingIgnored();
2632 while (previous && previous->AccessibilityIsIgnored()) {
2633 previous = previous->PreviousInPreOrderIncludingIgnored();
2634 }
2635 return previous;
2636 }
2637
ParentObject() const2638 AXObject* AXObject::ParentObject() const {
2639 if (IsDetached())
2640 return nullptr;
2641
2642 if (parent_)
2643 return parent_;
2644
2645 if (AXObjectCache().IsAriaOwned(this))
2646 return AXObjectCache().GetAriaOwnedParent(this);
2647
2648 return ComputeParent();
2649 }
2650
ParentObjectIfExists() const2651 AXObject* AXObject::ParentObjectIfExists() const {
2652 if (IsDetached())
2653 return nullptr;
2654
2655 if (parent_)
2656 return parent_;
2657
2658 return ComputeParentIfExists();
2659 }
2660
ParentObjectUnignored() const2661 AXObject* AXObject::ParentObjectUnignored() const {
2662 AXObject* parent;
2663 for (parent = ParentObject(); parent && parent->AccessibilityIsIgnored();
2664 parent = parent->ParentObject()) {
2665 }
2666
2667 return parent;
2668 }
2669
ParentObjectIncludedInTree() const2670 AXObject* AXObject::ParentObjectIncludedInTree() const {
2671 AXObject* parent;
2672 for (parent = ParentObject();
2673 parent && !parent->AccessibilityIsIncludedInTree();
2674 parent = parent->ParentObject()) {
2675 }
2676
2677 return parent;
2678 }
2679
2680 // Container widgets are those that a user tabs into and arrows around
2681 // sub-widgets
IsContainerWidget() const2682 bool AXObject::IsContainerWidget() const {
2683 switch (RoleValue()) {
2684 case ax::mojom::Role::kComboBoxGrouping:
2685 case ax::mojom::Role::kComboBoxMenuButton:
2686 case ax::mojom::Role::kGrid:
2687 case ax::mojom::Role::kListBox:
2688 case ax::mojom::Role::kMenuBar:
2689 case ax::mojom::Role::kMenu:
2690 case ax::mojom::Role::kRadioGroup:
2691 case ax::mojom::Role::kSpinButton:
2692 case ax::mojom::Role::kTabList:
2693 case ax::mojom::Role::kToolbar:
2694 case ax::mojom::Role::kTreeGrid:
2695 case ax::mojom::Role::kTree:
2696 return true;
2697 default:
2698 return false;
2699 }
2700 }
2701
ContainerWidget() const2702 AXObject* AXObject::ContainerWidget() const {
2703 AXObject* ancestor = ParentObjectUnignored();
2704 while (ancestor && !ancestor->IsContainerWidget())
2705 ancestor = ancestor->ParentObjectUnignored();
2706
2707 return ancestor;
2708 }
2709
UpdateChildrenIfNecessary()2710 void AXObject::UpdateChildrenIfNecessary() {
2711 if (!HasChildren())
2712 AddChildren();
2713 }
2714
ClearChildren()2715 void AXObject::ClearChildren() {
2716 // Detach all weak pointers from objects to their parents.
2717 for (const auto& child : children_) {
2718 if (child->parent_ == this)
2719 child->DetachFromParent();
2720 }
2721
2722 children_.clear();
2723 have_children_ = false;
2724 }
2725
AddAccessibleNodeChildren()2726 void AXObject::AddAccessibleNodeChildren() {
2727 Element* element = GetElement();
2728 if (!element)
2729 return;
2730
2731 AccessibleNode* accessible_node = element->ExistingAccessibleNode();
2732 if (!accessible_node)
2733 return;
2734
2735 for (const auto& child : accessible_node->GetChildren())
2736 children_.push_back(AXObjectCache().GetOrCreate(child));
2737 }
2738
GetElement() const2739 Element* AXObject::GetElement() const {
2740 return DynamicTo<Element>(GetNode());
2741 }
2742
GetDocument() const2743 Document* AXObject::GetDocument() const {
2744 LocalFrameView* frame_view = DocumentFrameView();
2745 if (!frame_view)
2746 return nullptr;
2747
2748 return frame_view->GetFrame().GetDocument();
2749 }
2750
RootScroller() const2751 AXObject* AXObject::RootScroller() const {
2752 Node* global_root_scroller = GetDocument()
2753 ->GetPage()
2754 ->GlobalRootScrollerController()
2755 .GlobalRootScroller();
2756 if (!global_root_scroller)
2757 return nullptr;
2758
2759 // Only return the root scroller if it's part of the same document.
2760 if (global_root_scroller->GetDocument() != GetDocument())
2761 return nullptr;
2762
2763 return AXObjectCache().GetOrCreate(global_root_scroller);
2764 }
2765
DocumentFrameView() const2766 LocalFrameView* AXObject::DocumentFrameView() const {
2767 const AXObject* object = this;
2768 while (object && !object->IsAXLayoutObject())
2769 object = object->ParentObject();
2770
2771 if (!object)
2772 return nullptr;
2773
2774 return object->DocumentFrameView();
2775 }
2776
Language() const2777 AtomicString AXObject::Language() const {
2778 // This method is used when the style engine is either not available on this
2779 // object, e.g. for canvas fallback content, or is unable to determine the
2780 // document's language. We use the following signals to detect the element's
2781 // language, in decreasing priority:
2782 // 1. The [language of a node] as defined in HTML, if known.
2783 // 2. The list of languages the browser sends in the [Accept-Language] header.
2784 // 3. The browser's default language.
2785
2786 const AtomicString& lang = GetAttribute(html_names::kLangAttr);
2787 if (!lang.IsEmpty())
2788 return lang;
2789
2790 AXObject* parent = ParentObject();
2791 if (parent)
2792 return parent->Language();
2793
2794 const Document* document = GetDocument();
2795 if (document) {
2796 // Fall back to the first content language specified in the meta tag.
2797 // This is not part of what the HTML5 Standard suggests but it still appears
2798 // to be necessary.
2799 if (document->ContentLanguage()) {
2800 const String content_languages = document->ContentLanguage();
2801 Vector<String> languages;
2802 content_languages.Split(',', languages);
2803 if (!languages.IsEmpty())
2804 return AtomicString(languages[0].StripWhiteSpace());
2805 }
2806
2807 if (document->GetPage()) {
2808 // Use the first accept language preference if present.
2809 const String accept_languages =
2810 document->GetPage()->GetChromeClient().AcceptLanguages();
2811 Vector<String> languages;
2812 accept_languages.Split(',', languages);
2813 if (!languages.IsEmpty())
2814 return AtomicString(languages[0].StripWhiteSpace());
2815 }
2816 }
2817
2818 // As a last resort, return the default language of the browser's UI.
2819 AtomicString default_language = DefaultLanguage();
2820 return default_language;
2821 }
2822
HasAttribute(const QualifiedName & attribute) const2823 bool AXObject::HasAttribute(const QualifiedName& attribute) const {
2824 Element* element = GetElement();
2825 if (!element)
2826 return false;
2827 if (element->FastHasAttribute(attribute))
2828 return true;
2829 return HasInternalsAttribute(*element, attribute);
2830 }
2831
GetAttribute(const QualifiedName & attribute) const2832 const AtomicString& AXObject::GetAttribute(
2833 const QualifiedName& attribute) const {
2834 Element* element = GetElement();
2835 if (!element)
2836 return g_null_atom;
2837 const AtomicString& value = element->FastGetAttribute(attribute);
2838 if (!value.IsNull())
2839 return value;
2840 return GetInternalsAttribute(*element, attribute);
2841 }
2842
HasInternalsAttribute(Element & element,const QualifiedName & attribute) const2843 bool AXObject::HasInternalsAttribute(Element& element,
2844 const QualifiedName& attribute) const {
2845 if (!element.DidAttachInternals())
2846 return false;
2847 return element.EnsureElementInternals().HasAttribute(attribute);
2848 }
2849
GetInternalsAttribute(Element & element,const QualifiedName & attribute) const2850 const AtomicString& AXObject::GetInternalsAttribute(
2851 Element& element,
2852 const QualifiedName& attribute) const {
2853 if (!element.DidAttachInternals())
2854 return g_null_atom;
2855 return element.EnsureElementInternals().FastGetAttribute(attribute);
2856 }
2857
2858 //
2859 // Scrollable containers.
2860 //
2861
IsScrollableContainer() const2862 bool AXObject::IsScrollableContainer() const {
2863 return !!GetScrollableAreaIfScrollable();
2864 }
2865
IsUserScrollable() const2866 bool AXObject::IsUserScrollable() const {
2867 // TODO(accessibility) Actually expose correct info on whether a doc is
2868 // is scrollable or not. Unfortunately IsScrollableContainer() always returns
2869 // true anyway. For now, just expose as scrollable unless overflow is hidden.
2870 if (IsWebArea()) {
2871 if (!GetScrollableAreaIfScrollable() || !GetLayoutObject())
2872 return false;
2873
2874 const ComputedStyle* style = GetLayoutObject()->Style();
2875 if (!style)
2876 return false;
2877
2878 return style->ScrollsOverflowY() || style->ScrollsOverflowX();
2879 }
2880
2881 return GetLayoutObject() && GetLayoutObject()->IsBox() &&
2882 ToLayoutBox(GetLayoutObject())->CanBeScrolledAndHasScrollableArea();
2883 }
2884
GetScrollOffset() const2885 IntPoint AXObject::GetScrollOffset() const {
2886 ScrollableArea* area = GetScrollableAreaIfScrollable();
2887 if (!area)
2888 return IntPoint();
2889
2890 return IntPoint(area->ScrollOffsetInt().Width(),
2891 area->ScrollOffsetInt().Height());
2892 }
2893
MinimumScrollOffset() const2894 IntPoint AXObject::MinimumScrollOffset() const {
2895 ScrollableArea* area = GetScrollableAreaIfScrollable();
2896 if (!area)
2897 return IntPoint();
2898
2899 return IntPoint(area->MinimumScrollOffsetInt().Width(),
2900 area->MinimumScrollOffsetInt().Height());
2901 }
2902
MaximumScrollOffset() const2903 IntPoint AXObject::MaximumScrollOffset() const {
2904 ScrollableArea* area = GetScrollableAreaIfScrollable();
2905 if (!area)
2906 return IntPoint();
2907
2908 return IntPoint(area->MaximumScrollOffsetInt().Width(),
2909 area->MaximumScrollOffsetInt().Height());
2910 }
2911
SetScrollOffset(const IntPoint & offset) const2912 void AXObject::SetScrollOffset(const IntPoint& offset) const {
2913 ScrollableArea* area = GetScrollableAreaIfScrollable();
2914 if (!area)
2915 return;
2916
2917 // TODO(bokan): This should potentially be a UserScroll.
2918 area->SetScrollOffset(ScrollOffset(offset.X(), offset.Y()),
2919 mojom::blink::ScrollType::kProgrammatic);
2920 }
2921
IsTableLikeRole() const2922 bool AXObject::IsTableLikeRole() const {
2923 switch (RoleValue()) {
2924 case ax::mojom::Role::kLayoutTable:
2925 case ax::mojom::Role::kTable:
2926 case ax::mojom::Role::kGrid:
2927 case ax::mojom::Role::kTreeGrid:
2928 return true;
2929 default:
2930 return false;
2931 }
2932 }
2933
IsTableRowLikeRole() const2934 bool AXObject::IsTableRowLikeRole() const {
2935 return RoleValue() == ax::mojom::Role::kRow ||
2936 RoleValue() == ax::mojom::Role::kLayoutTableRow;
2937 }
2938
IsTableCellLikeRole() const2939 bool AXObject::IsTableCellLikeRole() const {
2940 switch (RoleValue()) {
2941 case ax::mojom::Role::kLayoutTableCell:
2942 case ax::mojom::Role::kCell:
2943 case ax::mojom::Role::kColumnHeader:
2944 case ax::mojom::Role::kRowHeader:
2945 return true;
2946 default:
2947 return false;
2948 }
2949 }
2950
ColumnCount() const2951 unsigned AXObject::ColumnCount() const {
2952 if (!IsTableLikeRole())
2953 return 0;
2954
2955 unsigned max_column_count = 0;
2956 for (const auto& row : TableRowChildren()) {
2957 unsigned column_count = row->TableCellChildren().size();
2958 max_column_count = std::max(column_count, max_column_count);
2959 }
2960
2961 return max_column_count;
2962 }
2963
RowCount() const2964 unsigned AXObject::RowCount() const {
2965 if (!IsTableLikeRole())
2966 return 0;
2967
2968 return TableRowChildren().size();
2969 }
2970
ColumnHeaders(AXObjectVector & headers) const2971 void AXObject::ColumnHeaders(AXObjectVector& headers) const {
2972 if (!IsTableLikeRole())
2973 return;
2974
2975 for (const auto& row : TableRowChildren()) {
2976 for (const auto& cell : row->TableCellChildren()) {
2977 if (cell->RoleValue() == ax::mojom::Role::kColumnHeader)
2978 headers.push_back(cell);
2979 }
2980 }
2981 }
2982
RowHeaders(AXObjectVector & headers) const2983 void AXObject::RowHeaders(AXObjectVector& headers) const {
2984 if (!IsTableLikeRole())
2985 return;
2986
2987 for (const auto& row : TableRowChildren()) {
2988 for (const auto& cell : row->TableCellChildren()) {
2989 if (cell->RoleValue() == ax::mojom::Role::kRowHeader)
2990 headers.push_back(cell);
2991 }
2992 }
2993 }
2994
CellForColumnAndRow(unsigned target_column_index,unsigned target_row_index) const2995 AXObject* AXObject::CellForColumnAndRow(unsigned target_column_index,
2996 unsigned target_row_index) const {
2997 if (!IsTableLikeRole())
2998 return nullptr;
2999
3000 // Note that this code is only triggered if this is not a LayoutTable,
3001 // i.e. it's an ARIA grid/table.
3002 //
3003 // TODO(dmazzoni): delete this code or rename it "for testing only"
3004 // since it's only needed for Blink web tests and not for production.
3005 unsigned row_index = 0;
3006 for (const auto& row : TableRowChildren()) {
3007 unsigned column_index = 0;
3008 for (const auto& cell : row->TableCellChildren()) {
3009 if (target_column_index == column_index && target_row_index == row_index)
3010 return cell;
3011 column_index++;
3012 }
3013 row_index++;
3014 }
3015
3016 return nullptr;
3017 }
3018
AriaColumnCount() const3019 int AXObject::AriaColumnCount() const {
3020 if (!IsTableLikeRole())
3021 return 0;
3022
3023 int32_t col_count;
3024 if (!HasAOMPropertyOrARIAAttribute(AOMIntProperty::kColCount, col_count))
3025 return 0;
3026
3027 if (col_count > static_cast<int>(ColumnCount()))
3028 return col_count;
3029
3030 // Spec says that if all of the columns are present in the DOM, it
3031 // is not necessary to set this attribute as the user agent can
3032 // automatically calculate the total number of columns.
3033 // It returns 0 in order not to set this attribute.
3034 if (col_count == static_cast<int>(ColumnCount()) || col_count != -1)
3035 return 0;
3036
3037 return -1;
3038 }
3039
AriaRowCount() const3040 int AXObject::AriaRowCount() const {
3041 if (!IsTableLikeRole())
3042 return 0;
3043
3044 int32_t row_count;
3045 if (!HasAOMPropertyOrARIAAttribute(AOMIntProperty::kRowCount, row_count))
3046 return 0;
3047
3048 if (row_count > static_cast<int>(RowCount()))
3049 return row_count;
3050
3051 // Spec says that if all of the rows are present in the DOM, it is
3052 // not necessary to set this attribute as the user agent can
3053 // automatically calculate the total number of rows.
3054 // It returns 0 in order not to set this attribute.
3055 if (row_count == (int)RowCount() || row_count != -1)
3056 return 0;
3057
3058 // In the spec, -1 explicitly means an unknown number of rows.
3059 return -1;
3060 }
3061
ColumnIndex() const3062 unsigned AXObject::ColumnIndex() const {
3063 return 0;
3064 }
3065
RowIndex() const3066 unsigned AXObject::RowIndex() const {
3067 return 0;
3068 }
3069
ColumnSpan() const3070 unsigned AXObject::ColumnSpan() const {
3071 return IsTableCellLikeRole() ? 1 : 0;
3072 }
3073
RowSpan() const3074 unsigned AXObject::RowSpan() const {
3075 return IsTableCellLikeRole() ? 1 : 0;
3076 }
3077
AriaColumnIndex() const3078 unsigned AXObject::AriaColumnIndex() const {
3079 UpdateCachedAttributeValuesIfNeeded();
3080 return cached_aria_column_index_;
3081 }
3082
AriaRowIndex() const3083 unsigned AXObject::AriaRowIndex() const {
3084 UpdateCachedAttributeValuesIfNeeded();
3085 return cached_aria_row_index_;
3086 }
3087
ComputeAriaColumnIndex() const3088 unsigned AXObject::ComputeAriaColumnIndex() const {
3089 // Return the ARIA column index if it has been set. Otherwise return a default
3090 // value of 0.
3091 uint32_t col_index = 0;
3092 HasAOMPropertyOrARIAAttribute(AOMUIntProperty::kColIndex, col_index);
3093 return col_index;
3094 }
3095
ComputeAriaRowIndex() const3096 unsigned AXObject::ComputeAriaRowIndex() const {
3097 // Return the ARIA row index if it has been set. Otherwise return a default
3098 // value of 0.
3099 uint32_t row_index = 0;
3100 HasAOMPropertyOrARIAAttribute(AOMUIntProperty::kRowIndex, row_index);
3101 return row_index;
3102 }
3103
TableRowChildren() const3104 AXObject::AXObjectVector AXObject::TableRowChildren() const {
3105 AXObjectVector result;
3106 for (const auto& child : Children()) {
3107 if (child->IsTableRowLikeRole())
3108 result.push_back(child);
3109 else if (child->RoleValue() == ax::mojom::Role::kRowGroup)
3110 result.AppendVector(child->TableRowChildren());
3111 }
3112 return result;
3113 }
3114
TableCellChildren() const3115 AXObject::AXObjectVector AXObject::TableCellChildren() const {
3116 AXObjectVector result;
3117 for (const auto& child : Children()) {
3118 if (child->IsTableCellLikeRole())
3119 result.push_back(child);
3120 else if (child->RoleValue() == ax::mojom::Role::kGenericContainer)
3121 result.AppendVector(child->TableCellChildren());
3122 }
3123 return result;
3124 }
3125
TableRowParent() const3126 const AXObject* AXObject::TableRowParent() const {
3127 const AXObject* row = ParentObjectUnignored();
3128 while (row && !row->IsTableRowLikeRole() &&
3129 row->RoleValue() == ax::mojom::Role::kGenericContainer)
3130 row = row->ParentObjectUnignored();
3131 return row;
3132 }
3133
TableParent() const3134 const AXObject* AXObject::TableParent() const {
3135 const AXObject* table = ParentObjectUnignored();
3136 while (table && !table->IsTableLikeRole() &&
3137 table->RoleValue() == ax::mojom::Role::kGenericContainer)
3138 table = table->ParentObjectUnignored();
3139 return table;
3140 }
3141
GetDOMNodeId() const3142 int AXObject::GetDOMNodeId() const {
3143 Node* node = GetNode();
3144 if (node)
3145 return DOMNodeIds::IdForNode(node);
3146 return 0;
3147 }
3148
GetRelativeBounds(AXObject ** out_container,FloatRect & out_bounds_in_container,SkMatrix44 & out_container_transform,bool * clips_children) const3149 void AXObject::GetRelativeBounds(AXObject** out_container,
3150 FloatRect& out_bounds_in_container,
3151 SkMatrix44& out_container_transform,
3152 bool* clips_children) const {
3153 *out_container = nullptr;
3154 out_bounds_in_container = FloatRect();
3155 out_container_transform.setIdentity();
3156
3157 // First check if it has explicit bounds, for example if this element is tied
3158 // to a canvas path. When explicit coordinates are provided, the ID of the
3159 // explicit container element that the coordinates are relative to must be
3160 // provided too.
3161 if (!explicit_element_rect_.IsEmpty()) {
3162 *out_container = AXObjectCache().ObjectFromAXID(explicit_container_id_);
3163 if (*out_container) {
3164 out_bounds_in_container = FloatRect(explicit_element_rect_);
3165 return;
3166 }
3167 }
3168
3169 LayoutObject* layout_object = LayoutObjectForRelativeBounds();
3170 if (!layout_object)
3171 return;
3172
3173 if (clips_children) {
3174 if (IsWebArea())
3175 *clips_children = true;
3176 else
3177 *clips_children = layout_object->HasOverflowClip();
3178 }
3179
3180 if (IsWebArea()) {
3181 if (layout_object->GetFrame()->View()) {
3182 out_bounds_in_container.SetSize(
3183 FloatSize(layout_object->GetFrame()->View()->Size()));
3184 }
3185 return;
3186 }
3187
3188 // First compute the container. The container must be an ancestor in the
3189 // accessibility tree, and its LayoutObject must be an ancestor in the layout
3190 // tree. Get the first such ancestor that's either scrollable or has a paint
3191 // layer.
3192 AXObject* container = ParentObjectUnignored();
3193 LayoutObject* container_layout_object = nullptr;
3194 if (layout_object->IsFixedPositioned()) {
3195 // If it's a fixed position element, the container should simply be the
3196 // root web area.
3197 container = AXObjectCache().GetOrCreate(GetDocument());
3198 } else {
3199 while (container) {
3200 container_layout_object = container->GetLayoutObject();
3201 if (container_layout_object && container_layout_object->IsBox() &&
3202 layout_object->IsDescendantOf(container_layout_object)) {
3203 if (container->IsScrollableContainer() ||
3204 container_layout_object->HasLayer()) {
3205 if (layout_object->IsAbsolutePositioned()) {
3206 // If it's absolutely positioned, the container must be the
3207 // nearest positioned container, or the root.
3208 if (container->IsWebArea())
3209 break;
3210 if (container_layout_object->IsPositioned())
3211 break;
3212 } else {
3213 break;
3214 }
3215 }
3216 }
3217
3218 container = container->ParentObjectUnignored();
3219 }
3220 }
3221
3222 if (!container)
3223 return;
3224 *out_container = container;
3225 out_bounds_in_container =
3226 layout_object->LocalBoundingBoxRectForAccessibility();
3227
3228 // Frames need to take their border and padding into account so the
3229 // child element's computed position will be correct.
3230 if (layout_object->IsBox() && layout_object->GetNode() &&
3231 layout_object->GetNode()->IsFrameOwnerElement()) {
3232 out_bounds_in_container =
3233 FloatRect(ToLayoutBox(layout_object)->PhysicalContentBoxRect());
3234 }
3235
3236 // If the container has a scroll offset, subtract that out because we want our
3237 // bounds to be relative to the *unscrolled* position of the container object.
3238 if (auto* scrollable_area = container->GetScrollableAreaIfScrollable())
3239 out_bounds_in_container.Move(scrollable_area->GetScrollOffset());
3240
3241 // Compute the transform between the container's coordinate space and this
3242 // object.
3243 TransformationMatrix transform = layout_object->LocalToAncestorTransform(
3244 ToLayoutBoxModelObject(container_layout_object));
3245
3246 // If the transform is just a simple translation, apply that to the
3247 // bounding box, but if it's a non-trivial transformation like a rotation,
3248 // scaling, etc. then return the full matrix instead.
3249 if (transform.IsIdentityOr2DTranslation()) {
3250 out_bounds_in_container.Move(transform.To2DTranslation());
3251 } else {
3252 out_container_transform = TransformationMatrix::ToSkMatrix44(transform);
3253 }
3254 }
3255
LocalBoundingBoxRectForAccessibility()3256 FloatRect AXObject::LocalBoundingBoxRectForAccessibility() {
3257 if (!GetLayoutObject())
3258 return FloatRect();
3259 DCHECK(GetLayoutObject()->IsText());
3260 UpdateCachedAttributeValuesIfNeeded();
3261 return cached_local_bounding_box_rect_for_accessibility_;
3262 }
3263
GetBoundsInFrameCoordinates() const3264 LayoutRect AXObject::GetBoundsInFrameCoordinates() const {
3265 AXObject* container = nullptr;
3266 FloatRect bounds;
3267 SkMatrix44 transform;
3268 GetRelativeBounds(&container, bounds, transform);
3269 FloatRect computed_bounds(0, 0, bounds.Width(), bounds.Height());
3270 while (container && container != this) {
3271 computed_bounds.Move(bounds.X(), bounds.Y());
3272 if (!container->IsWebArea()) {
3273 computed_bounds.Move(-container->GetScrollOffset().X(),
3274 -container->GetScrollOffset().Y());
3275 }
3276 if (!transform.isIdentity()) {
3277 TransformationMatrix transformation_matrix(transform);
3278 transformation_matrix.MapRect(computed_bounds);
3279 }
3280 container->GetRelativeBounds(&container, bounds, transform);
3281 }
3282 return LayoutRect(computed_bounds);
3283 }
3284
3285 //
3286 // Modify or take an action on an object.
3287 //
3288
RequestDecrementAction()3289 bool AXObject::RequestDecrementAction() {
3290 Event* event =
3291 Event::CreateCancelable(event_type_names::kAccessibledecrement);
3292 if (DispatchEventToAOMEventListeners(*event))
3293 return true;
3294
3295 return OnNativeDecrementAction();
3296 }
3297
RequestClickAction()3298 bool AXObject::RequestClickAction() {
3299 Event* event = Event::CreateCancelable(event_type_names::kAccessibleclick);
3300 if (DispatchEventToAOMEventListeners(*event))
3301 return true;
3302
3303 return OnNativeClickAction();
3304 }
3305
OnNativeClickAction()3306 bool AXObject::OnNativeClickAction() {
3307 Document* document = GetDocument();
3308 if (!document)
3309 return false;
3310
3311 LocalFrame::NotifyUserActivation(document->GetFrame());
3312
3313 Element* element = GetElement();
3314 if (!element && GetNode())
3315 element = GetNode()->parentElement();
3316
3317 if (IsTextControl())
3318 return OnNativeFocusAction();
3319
3320 if (element) {
3321 // Always set the sequential focus navigation starting point.
3322 // Even if this element isn't focusable, if you press "Tab" it will
3323 // start the search from this element.
3324 GetDocument()->SetSequentialFocusNavigationStartingPoint(element);
3325
3326 // Explicitly focus the element if it's focusable but not currently
3327 // the focused element, to be consistent with
3328 // EventHandler::HandleMousePressEvent.
3329 if (element->IsMouseFocusable() && !element->IsFocusedElementInDocument()) {
3330 Page* const page = GetDocument()->GetPage();
3331 if (page) {
3332 page->GetFocusController().SetFocusedElement(
3333 element, GetDocument()->GetFrame(),
3334 FocusParams(SelectionBehaviorOnFocus::kNone,
3335 mojom::blink::FocusType::kMouse, nullptr));
3336 }
3337 }
3338
3339 // For most elements, AccessKeyAction triggers sending a simulated
3340 // click, including simulating the mousedown, mouseup, and click events.
3341 element->AccessKeyAction(true);
3342 return true;
3343 }
3344
3345 if (CanSetFocusAttribute())
3346 return OnNativeFocusAction();
3347
3348 return false;
3349 }
3350
RequestFocusAction()3351 bool AXObject::RequestFocusAction() {
3352 Event* event = Event::CreateCancelable(event_type_names::kAccessiblefocus);
3353 if (DispatchEventToAOMEventListeners(*event))
3354 return true;
3355
3356 return OnNativeFocusAction();
3357 }
3358
RequestIncrementAction()3359 bool AXObject::RequestIncrementAction() {
3360 Event* event =
3361 Event::CreateCancelable(event_type_names::kAccessibleincrement);
3362 if (DispatchEventToAOMEventListeners(*event))
3363 return true;
3364
3365 return OnNativeIncrementAction();
3366 }
3367
RequestScrollToGlobalPointAction(const IntPoint & point)3368 bool AXObject::RequestScrollToGlobalPointAction(const IntPoint& point) {
3369 return OnNativeScrollToGlobalPointAction(point);
3370 }
3371
RequestScrollToMakeVisibleAction()3372 bool AXObject::RequestScrollToMakeVisibleAction() {
3373 Event* event =
3374 Event::CreateCancelable(event_type_names::kAccessiblescrollintoview);
3375 if (DispatchEventToAOMEventListeners(*event))
3376 return true;
3377
3378 return OnNativeScrollToMakeVisibleAction();
3379 }
3380
RequestScrollToMakeVisibleWithSubFocusAction(const IntRect & subfocus,blink::mojom::blink::ScrollAlignment horizontal_scroll_alignment,blink::mojom::blink::ScrollAlignment vertical_scroll_alignment)3381 bool AXObject::RequestScrollToMakeVisibleWithSubFocusAction(
3382 const IntRect& subfocus,
3383 blink::mojom::blink::ScrollAlignment horizontal_scroll_alignment,
3384 blink::mojom::blink::ScrollAlignment vertical_scroll_alignment) {
3385 return OnNativeScrollToMakeVisibleWithSubFocusAction(
3386 subfocus, horizontal_scroll_alignment, vertical_scroll_alignment);
3387 }
3388
RequestSetSelectedAction(bool selected)3389 bool AXObject::RequestSetSelectedAction(bool selected) {
3390 return OnNativeSetSelectedAction(selected);
3391 }
3392
RequestSetSequentialFocusNavigationStartingPointAction()3393 bool AXObject::RequestSetSequentialFocusNavigationStartingPointAction() {
3394 return OnNativeSetSequentialFocusNavigationStartingPointAction();
3395 }
3396
RequestSetValueAction(const String & value)3397 bool AXObject::RequestSetValueAction(const String& value) {
3398 return OnNativeSetValueAction(value);
3399 }
3400
RequestShowContextMenuAction()3401 bool AXObject::RequestShowContextMenuAction() {
3402 Event* event =
3403 Event::CreateCancelable(event_type_names::kAccessiblecontextmenu);
3404 if (DispatchEventToAOMEventListeners(*event))
3405 return true;
3406
3407 return OnNativeShowContextMenuAction();
3408 }
3409
InternalClearAccessibilityFocusAction()3410 bool AXObject::InternalClearAccessibilityFocusAction() {
3411 return false;
3412 }
3413
InternalSetAccessibilityFocusAction()3414 bool AXObject::InternalSetAccessibilityFocusAction() {
3415 return false;
3416 }
3417
OnNativeScrollToMakeVisibleAction() const3418 bool AXObject::OnNativeScrollToMakeVisibleAction() const {
3419 Node* node = GetNode();
3420 if (!node || !node->isConnected())
3421 return false;
3422
3423 // Node might not have a LayoutObject due to the fact that it is in a locked
3424 // subtree. Force the update to create the LayoutObject (and update position
3425 // information) for this node.
3426 DisplayLockUtilities::ScopedChainForcedUpdate scoped_force_update(node);
3427 GetDocument()->UpdateStyleAndLayout(DocumentUpdateReason::kDisplayLock);
3428
3429 LayoutObject* layout_object = node->GetLayoutObject();
3430 if (!layout_object)
3431 return false;
3432 PhysicalRect target_rect(layout_object->AbsoluteBoundingBoxRect());
3433 layout_object->ScrollRectToVisible(
3434 target_rect,
3435 ScrollAlignment::CreateScrollIntoViewParams(
3436 ScrollAlignment::CenterIfNeeded(), ScrollAlignment::CenterIfNeeded(),
3437 mojom::blink::ScrollType::kProgrammatic, false,
3438 mojom::blink::ScrollBehavior::kAuto));
3439 AXObjectCache().PostNotification(
3440 AXObjectCache().GetOrCreate(GetDocument()->GetLayoutView()),
3441 ax::mojom::Event::kLocationChanged);
3442 return true;
3443 }
3444
OnNativeScrollToMakeVisibleWithSubFocusAction(const IntRect & rect,blink::mojom::blink::ScrollAlignment horizontal_scroll_alignment,blink::mojom::blink::ScrollAlignment vertical_scroll_alignment) const3445 bool AXObject::OnNativeScrollToMakeVisibleWithSubFocusAction(
3446 const IntRect& rect,
3447 blink::mojom::blink::ScrollAlignment horizontal_scroll_alignment,
3448 blink::mojom::blink::ScrollAlignment vertical_scroll_alignment) const {
3449 Node* node = GetNode();
3450 LayoutObject* layout_object = node ? node->GetLayoutObject() : nullptr;
3451 if (!layout_object || !node->isConnected())
3452 return false;
3453 PhysicalRect target_rect =
3454 layout_object->LocalToAbsoluteRect(PhysicalRect(rect));
3455 layout_object->ScrollRectToVisible(
3456 target_rect, ScrollAlignment::CreateScrollIntoViewParams(
3457 horizontal_scroll_alignment, vertical_scroll_alignment,
3458 mojom::blink::ScrollType::kProgrammatic,
3459 false /* make_visible_in_visual_viewport */,
3460 mojom::blink::ScrollBehavior::kAuto));
3461 AXObjectCache().PostNotification(
3462 AXObjectCache().GetOrCreate(GetDocument()->GetLayoutView()),
3463 ax::mojom::Event::kLocationChanged);
3464 return true;
3465 }
3466
OnNativeScrollToGlobalPointAction(const IntPoint & global_point) const3467 bool AXObject::OnNativeScrollToGlobalPointAction(
3468 const IntPoint& global_point) const {
3469 Node* node = GetNode();
3470 LayoutObject* layout_object = node ? node->GetLayoutObject() : nullptr;
3471 if (!layout_object || !node->isConnected())
3472 return false;
3473 PhysicalRect target_rect(layout_object->AbsoluteBoundingBoxRect());
3474 target_rect.Move(-PhysicalOffset(global_point));
3475 layout_object->ScrollRectToVisible(
3476 target_rect,
3477 ScrollAlignment::CreateScrollIntoViewParams(
3478 ScrollAlignment::LeftAlways(), ScrollAlignment::TopAlways(),
3479 mojom::blink::ScrollType::kProgrammatic, false,
3480 mojom::blink::ScrollBehavior::kAuto));
3481 AXObjectCache().PostNotification(
3482 AXObjectCache().GetOrCreate(GetDocument()->GetLayoutView()),
3483 ax::mojom::Event::kLocationChanged);
3484 return true;
3485 }
3486
OnNativeSetSequentialFocusNavigationStartingPointAction()3487 bool AXObject::OnNativeSetSequentialFocusNavigationStartingPointAction() {
3488 // Call it on the nearest ancestor that overrides this with a specific
3489 // implementation.
3490 if (ParentObject()) {
3491 return ParentObject()
3492 ->OnNativeSetSequentialFocusNavigationStartingPointAction();
3493 }
3494 return false;
3495 }
3496
OnNativeDecrementAction()3497 bool AXObject::OnNativeDecrementAction() {
3498 return false;
3499 }
3500
OnNativeFocusAction()3501 bool AXObject::OnNativeFocusAction() {
3502 return false;
3503 }
3504
OnNativeIncrementAction()3505 bool AXObject::OnNativeIncrementAction() {
3506 return false;
3507 }
3508
OnNativeSetValueAction(const String &)3509 bool AXObject::OnNativeSetValueAction(const String&) {
3510 return false;
3511 }
3512
OnNativeSetSelectedAction(bool)3513 bool AXObject::OnNativeSetSelectedAction(bool) {
3514 return false;
3515 }
3516
OnNativeShowContextMenuAction()3517 bool AXObject::OnNativeShowContextMenuAction() {
3518 Element* element = GetElement();
3519 if (!element)
3520 element = ParentObject() ? ParentObject()->GetElement() : nullptr;
3521 if (!element)
3522 return false;
3523
3524 Document* document = GetDocument();
3525 if (!document || !document->GetFrame())
3526 return false;
3527
3528 ContextMenuAllowedScope scope;
3529 document->GetFrame()->GetEventHandler().ShowNonLocatedContextMenu(element);
3530 return true;
3531 }
3532
SelectionChanged()3533 void AXObject::SelectionChanged() {
3534 if (AXObject* parent = ParentObjectIfExists())
3535 parent->SelectionChanged();
3536 }
3537
3538 // static
IsARIAControl(ax::mojom::Role aria_role)3539 bool AXObject::IsARIAControl(ax::mojom::Role aria_role) {
3540 return IsARIAInput(aria_role) || aria_role == ax::mojom::Role::kButton ||
3541 aria_role == ax::mojom::Role::kComboBoxMenuButton ||
3542 aria_role == ax::mojom::Role::kSlider;
3543 }
3544
3545 // static
IsARIAInput(ax::mojom::Role aria_role)3546 bool AXObject::IsARIAInput(ax::mojom::Role aria_role) {
3547 return aria_role == ax::mojom::Role::kRadioButton ||
3548 aria_role == ax::mojom::Role::kCheckBox ||
3549 aria_role == ax::mojom::Role::kTextField ||
3550 aria_role == ax::mojom::Role::kSwitch ||
3551 aria_role == ax::mojom::Role::kSearchBox ||
3552 aria_role == ax::mojom::Role::kTextFieldWithComboBox;
3553 }
3554
AriaRoleToWebCoreRole(const String & value)3555 ax::mojom::Role AXObject::AriaRoleToWebCoreRole(const String& value) {
3556 DCHECK(!value.IsEmpty());
3557
3558 static const ARIARoleMap* role_map = CreateARIARoleMap();
3559
3560 Vector<String> role_vector;
3561 value.Split(' ', role_vector);
3562 ax::mojom::Role role = ax::mojom::Role::kUnknown;
3563 for (const auto& child : role_vector) {
3564 role = role_map->at(child);
3565 if (role != ax::mojom::Role::kUnknown)
3566 return role;
3567 }
3568
3569 return role;
3570 }
3571
NameFromSelectedOption(bool recursive) const3572 bool AXObject::NameFromSelectedOption(bool recursive) const {
3573 switch (RoleValue()) {
3574 // Step 2E from: http://www.w3.org/TR/accname-aam-1.1
3575 case ax::mojom::Role::kComboBoxGrouping:
3576 case ax::mojom::Role::kComboBoxMenuButton:
3577 case ax::mojom::Role::kListBox:
3578 return recursive;
3579 // This can be either a button widget with a non-false value of
3580 // aria-haspopup or a select element with size of 1.
3581 case ax::mojom::Role::kPopUpButton:
3582 return DynamicTo<HTMLSelectElement>(*GetNode()) ? recursive : false;
3583 default:
3584 return false;
3585 }
3586 }
3587
NameFromContents(bool recursive) const3588 bool AXObject::NameFromContents(bool recursive) const {
3589 // ARIA 1.1, section 5.2.7.5.
3590 bool result = false;
3591
3592 switch (RoleValue()) {
3593 // ----- NameFrom: contents -------------------------
3594 // Get their own name from contents, or contribute to ancestors
3595 case ax::mojom::Role::kAnchor:
3596 case ax::mojom::Role::kButton:
3597 case ax::mojom::Role::kCell:
3598 case ax::mojom::Role::kCheckBox:
3599 case ax::mojom::Role::kColumnHeader:
3600 case ax::mojom::Role::kComboBoxMenuButton:
3601 case ax::mojom::Role::kDocBackLink:
3602 case ax::mojom::Role::kDocBiblioRef:
3603 case ax::mojom::Role::kDocNoteRef:
3604 case ax::mojom::Role::kDocGlossRef:
3605 case ax::mojom::Role::kDisclosureTriangle:
3606 case ax::mojom::Role::kHeading:
3607 case ax::mojom::Role::kLayoutTableCell:
3608 case ax::mojom::Role::kLineBreak:
3609 case ax::mojom::Role::kLink:
3610 case ax::mojom::Role::kListBoxOption:
3611 case ax::mojom::Role::kMenuButton:
3612 case ax::mojom::Role::kMenuItem:
3613 case ax::mojom::Role::kMenuItemCheckBox:
3614 case ax::mojom::Role::kMenuItemRadio:
3615 case ax::mojom::Role::kMenuListOption:
3616 case ax::mojom::Role::kPopUpButton:
3617 case ax::mojom::Role::kPortal:
3618 case ax::mojom::Role::kRadioButton:
3619 case ax::mojom::Role::kRowHeader:
3620 case ax::mojom::Role::kStaticText:
3621 case ax::mojom::Role::kSwitch:
3622 case ax::mojom::Role::kTab:
3623 case ax::mojom::Role::kToggleButton:
3624 case ax::mojom::Role::kTreeItem:
3625 case ax::mojom::Role::kTooltip:
3626 result = true;
3627 break;
3628
3629 // ----- No name from contents -------------------------
3630 // These never have or contribute a name from contents, as they are
3631 // containers for many subobjects. Superset of nameFrom:author ARIA roles.
3632 case ax::mojom::Role::kAlert:
3633 case ax::mojom::Role::kAlertDialog:
3634 case ax::mojom::Role::kApplication:
3635 case ax::mojom::Role::kAudio:
3636 case ax::mojom::Role::kArticle:
3637 case ax::mojom::Role::kBanner:
3638 case ax::mojom::Role::kBlockquote:
3639 case ax::mojom::Role::kCaret:
3640 case ax::mojom::Role::kClient:
3641 case ax::mojom::Role::kColorWell:
3642 case ax::mojom::Role::kColumn:
3643 case ax::mojom::Role::kComboBoxGrouping:
3644 case ax::mojom::Role::kComment:
3645 case ax::mojom::Role::kComplementary:
3646 case ax::mojom::Role::kContentInfo:
3647 case ax::mojom::Role::kDate:
3648 case ax::mojom::Role::kDateTime:
3649 case ax::mojom::Role::kDesktop:
3650 case ax::mojom::Role::kDialog:
3651 case ax::mojom::Role::kDirectory:
3652 case ax::mojom::Role::kDocCover:
3653 case ax::mojom::Role::kDocBiblioEntry:
3654 case ax::mojom::Role::kDocEndnote:
3655 case ax::mojom::Role::kDocFootnote:
3656 case ax::mojom::Role::kDocPageBreak:
3657 case ax::mojom::Role::kDocAbstract:
3658 case ax::mojom::Role::kDocAcknowledgments:
3659 case ax::mojom::Role::kDocAfterword:
3660 case ax::mojom::Role::kDocAppendix:
3661 case ax::mojom::Role::kDocBibliography:
3662 case ax::mojom::Role::kDocChapter:
3663 case ax::mojom::Role::kDocColophon:
3664 case ax::mojom::Role::kDocConclusion:
3665 case ax::mojom::Role::kDocCredit:
3666 case ax::mojom::Role::kDocCredits:
3667 case ax::mojom::Role::kDocDedication:
3668 case ax::mojom::Role::kDocEndnotes:
3669 case ax::mojom::Role::kDocEpigraph:
3670 case ax::mojom::Role::kDocEpilogue:
3671 case ax::mojom::Role::kDocErrata:
3672 case ax::mojom::Role::kDocExample:
3673 case ax::mojom::Role::kDocForeword:
3674 case ax::mojom::Role::kDocGlossary:
3675 case ax::mojom::Role::kDocIndex:
3676 case ax::mojom::Role::kDocIntroduction:
3677 case ax::mojom::Role::kDocNotice:
3678 case ax::mojom::Role::kDocPageList:
3679 case ax::mojom::Role::kDocPart:
3680 case ax::mojom::Role::kDocPreface:
3681 case ax::mojom::Role::kDocPrologue:
3682 case ax::mojom::Role::kDocPullquote:
3683 case ax::mojom::Role::kDocQna:
3684 case ax::mojom::Role::kDocSubtitle:
3685 case ax::mojom::Role::kDocTip:
3686 case ax::mojom::Role::kDocToc:
3687 case ax::mojom::Role::kDocument:
3688 case ax::mojom::Role::kEmbeddedObject:
3689 case ax::mojom::Role::kFeed:
3690 case ax::mojom::Role::kFigure:
3691 case ax::mojom::Role::kForm:
3692 case ax::mojom::Role::kGraphicsDocument:
3693 case ax::mojom::Role::kGraphicsObject:
3694 case ax::mojom::Role::kGraphicsSymbol:
3695 case ax::mojom::Role::kGrid:
3696 case ax::mojom::Role::kGroup:
3697 case ax::mojom::Role::kHeader:
3698 case ax::mojom::Role::kIframePresentational:
3699 case ax::mojom::Role::kIframe:
3700 case ax::mojom::Role::kImage:
3701 case ax::mojom::Role::kInputTime:
3702 case ax::mojom::Role::kKeyboard:
3703 case ax::mojom::Role::kListBox:
3704 case ax::mojom::Role::kListGrid:
3705 case ax::mojom::Role::kLog:
3706 case ax::mojom::Role::kMain:
3707 case ax::mojom::Role::kMarquee:
3708 case ax::mojom::Role::kMath:
3709 case ax::mojom::Role::kMenuListPopup:
3710 case ax::mojom::Role::kMenu:
3711 case ax::mojom::Role::kMenuBar:
3712 case ax::mojom::Role::kMeter:
3713 case ax::mojom::Role::kNavigation:
3714 case ax::mojom::Role::kNote:
3715 case ax::mojom::Role::kPane:
3716 case ax::mojom::Role::kPluginObject:
3717 case ax::mojom::Role::kProgressIndicator:
3718 case ax::mojom::Role::kRadioGroup:
3719 case ax::mojom::Role::kRowGroup:
3720 case ax::mojom::Role::kScrollBar:
3721 case ax::mojom::Role::kScrollView:
3722 case ax::mojom::Role::kSearch:
3723 case ax::mojom::Role::kSearchBox:
3724 case ax::mojom::Role::kSplitter:
3725 case ax::mojom::Role::kSlider:
3726 case ax::mojom::Role::kSpinButton:
3727 case ax::mojom::Role::kStatus:
3728 case ax::mojom::Role::kSliderThumb:
3729 case ax::mojom::Role::kSuggestion:
3730 case ax::mojom::Role::kSvgRoot:
3731 case ax::mojom::Role::kTable:
3732 case ax::mojom::Role::kTableHeaderContainer:
3733 case ax::mojom::Role::kTabList:
3734 case ax::mojom::Role::kTabPanel:
3735 case ax::mojom::Role::kTerm:
3736 case ax::mojom::Role::kTextField:
3737 case ax::mojom::Role::kTextFieldWithComboBox:
3738 case ax::mojom::Role::kTitleBar:
3739 case ax::mojom::Role::kTimer:
3740 case ax::mojom::Role::kToolbar:
3741 case ax::mojom::Role::kTree:
3742 case ax::mojom::Role::kTreeGrid:
3743 case ax::mojom::Role::kVideo:
3744 case ax::mojom::Role::kWebArea:
3745 case ax::mojom::Role::kWebView:
3746 result = false;
3747 break;
3748
3749 // ----- Conditional: contribute to ancestor only, unless focusable -------
3750 // Some objects can contribute their contents to ancestor names, but
3751 // only have their own name if they are focusable
3752 case ax::mojom::Role::kAbbr:
3753 case ax::mojom::Role::kCanvas:
3754 case ax::mojom::Role::kCaption:
3755 case ax::mojom::Role::kCode:
3756 case ax::mojom::Role::kContentDeletion:
3757 case ax::mojom::Role::kContentInsertion:
3758 case ax::mojom::Role::kDefinition:
3759 case ax::mojom::Role::kDescriptionListDetail:
3760 case ax::mojom::Role::kDescriptionList:
3761 case ax::mojom::Role::kDescriptionListTerm:
3762 case ax::mojom::Role::kDetails:
3763 case ax::mojom::Role::kEmphasis:
3764 case ax::mojom::Role::kFigcaption:
3765 case ax::mojom::Role::kFooter:
3766 case ax::mojom::Role::kFooterAsNonLandmark:
3767 case ax::mojom::Role::kGenericContainer:
3768 case ax::mojom::Role::kHeaderAsNonLandmark:
3769 case ax::mojom::Role::kIgnored:
3770 case ax::mojom::Role::kImageMap:
3771 case ax::mojom::Role::kInlineTextBox:
3772 case ax::mojom::Role::kLabelText:
3773 case ax::mojom::Role::kLayoutTable:
3774 case ax::mojom::Role::kLayoutTableRow:
3775 case ax::mojom::Role::kLegend:
3776 case ax::mojom::Role::kList:
3777 case ax::mojom::Role::kListItem:
3778 case ax::mojom::Role::kListMarker:
3779 case ax::mojom::Role::kMark:
3780 case ax::mojom::Role::kNone:
3781 case ax::mojom::Role::kParagraph:
3782 case ax::mojom::Role::kPre:
3783 case ax::mojom::Role::kPresentational:
3784 case ax::mojom::Role::kRegion:
3785 // Spec says we should always expose the name on rows,
3786 // but for performance reasons we only do it
3787 // if the row might receive focus
3788 case ax::mojom::Role::kRow:
3789 case ax::mojom::Role::kRuby:
3790 case ax::mojom::Role::kRubyAnnotation:
3791 case ax::mojom::Role::kSection:
3792 case ax::mojom::Role::kStrong:
3793 case ax::mojom::Role::kTime:
3794 if (recursive) {
3795 // Use contents if part of a recursive name computation.
3796 result = true;
3797 } else {
3798 // Use contents if focusable, so that there is a name in the case
3799 // where the author mistakenly forgot to provide one.
3800 // Exceptions:
3801 // 1.Elements with contenteditable, where using the contents as a name
3802 // would cause them to be double-announced.
3803 // 2.Containers with aria-activedescendant, where the focus is being
3804 // forwarded somewhere else.
3805 // TODO(accessibility) Scrollables are currently whitelisted here in
3806 // order to keep the current behavior. In the future, this can be
3807 // removed because this code will be handled in IsFocusable(), once
3808 // KeyboardFocusableScrollersEnabled is permanently enabled.
3809 // Note: this uses the same scrollable check that element.cc uses.
3810 bool is_focusable_scrollable =
3811 RuntimeEnabledFeatures::KeyboardFocusableScrollersEnabled() &&
3812 IsUserScrollable();
3813 bool is_focusable = is_focusable_scrollable || CanSetFocusAttribute();
3814 result = is_focusable && !IsEditable() &&
3815 !GetAOMPropertyOrARIAAttribute(
3816 AOMRelationProperty::kActiveDescendant);
3817 }
3818 break;
3819
3820 case ax::mojom::Role::kPdfActionableHighlight:
3821 LOG(ERROR) << "PDF specific highlight role, Blink shouldn't generate "
3822 "this role type";
3823 NOTREACHED();
3824 break;
3825
3826 // A root web area normally only computes its name from the document title,
3827 // but a root web area inside a portal's main frame should compute its name
3828 // from its contents. This name is used by the portal element that hosts
3829 // this portal.
3830 case ax::mojom::Role::kRootWebArea: {
3831 DCHECK(GetNode());
3832 const Document& document = GetNode()->GetDocument();
3833 bool is_main_frame =
3834 document.GetFrame() && document.GetFrame()->IsMainFrame();
3835 bool is_inside_portal =
3836 document.GetPage() && document.GetPage()->InsidePortal();
3837 return is_inside_portal && is_main_frame;
3838 }
3839
3840 case ax::mojom::Role::kUnknown:
3841 case ax::mojom::Role::kMaxValue:
3842 LOG(ERROR) << "ax::mojom::Role::kUnknown for " << GetNode();
3843 NOTREACHED();
3844 break;
3845 }
3846
3847 return result;
3848 }
3849
SupportsARIAReadOnly() const3850 bool AXObject::SupportsARIAReadOnly() const {
3851 switch (RoleValue()) {
3852 case ax::mojom::Role::kCell:
3853 case ax::mojom::Role::kCheckBox:
3854 case ax::mojom::Role::kColorWell:
3855 case ax::mojom::Role::kColumnHeader:
3856 case ax::mojom::Role::kComboBoxGrouping:
3857 case ax::mojom::Role::kComboBoxMenuButton:
3858 case ax::mojom::Role::kDate:
3859 case ax::mojom::Role::kDateTime:
3860 case ax::mojom::Role::kGrid:
3861 case ax::mojom::Role::kInputTime:
3862 case ax::mojom::Role::kListBox:
3863 case ax::mojom::Role::kMenuButton:
3864 case ax::mojom::Role::kMenuItemCheckBox:
3865 case ax::mojom::Role::kMenuItemRadio:
3866 case ax::mojom::Role::kPopUpButton:
3867 case ax::mojom::Role::kRadioGroup:
3868 case ax::mojom::Role::kRowHeader:
3869 case ax::mojom::Role::kSearchBox:
3870 case ax::mojom::Role::kSlider:
3871 case ax::mojom::Role::kSpinButton:
3872 case ax::mojom::Role::kSwitch:
3873 case ax::mojom::Role::kTextField:
3874 case ax::mojom::Role::kTextFieldWithComboBox:
3875 case ax::mojom::Role::kToggleButton:
3876 case ax::mojom::Role::kTreeGrid:
3877 return true;
3878 default:
3879 break;
3880 }
3881 return false;
3882 }
3883
ButtonRoleType() const3884 ax::mojom::Role AXObject::ButtonRoleType() const {
3885 // If aria-pressed is present, then it should be exposed as a toggle button.
3886 // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed
3887 if (AriaPressedIsPresent())
3888 return ax::mojom::Role::kToggleButton;
3889 if (HasPopup() != ax::mojom::HasPopup::kFalse)
3890 return ax::mojom::Role::kPopUpButton;
3891 // We don't contemplate RadioButtonRole, as it depends on the input
3892 // type.
3893
3894 return ax::mojom::Role::kButton;
3895 }
3896
3897 // static
RoleName(ax::mojom::Role role)3898 const AtomicString& AXObject::RoleName(ax::mojom::Role role) {
3899 static const Vector<AtomicString>* role_name_vector = CreateRoleNameVector();
3900
3901 return role_name_vector->at(static_cast<wtf_size_t>(role));
3902 }
3903
3904 // static
InternalRoleName(ax::mojom::Role role)3905 const AtomicString& AXObject::InternalRoleName(ax::mojom::Role role) {
3906 static const Vector<AtomicString>* internal_role_name_vector =
3907 CreateInternalRoleNameVector();
3908
3909 return internal_role_name_vector->at(static_cast<wtf_size_t>(role));
3910 }
3911
3912 // static
LowestCommonAncestor(const AXObject & first,const AXObject & second,int * index_in_ancestor1,int * index_in_ancestor2)3913 const AXObject* AXObject::LowestCommonAncestor(const AXObject& first,
3914 const AXObject& second,
3915 int* index_in_ancestor1,
3916 int* index_in_ancestor2) {
3917 *index_in_ancestor1 = -1;
3918 *index_in_ancestor2 = -1;
3919
3920 if (first.IsDetached() || second.IsDetached())
3921 return nullptr;
3922
3923 if (first == second)
3924 return &first;
3925
3926 HeapVector<Member<const AXObject>> ancestors1;
3927 ancestors1.push_back(&first);
3928 while (ancestors1.back())
3929 ancestors1.push_back(ancestors1.back()->ParentObjectIncludedInTree());
3930
3931 HeapVector<Member<const AXObject>> ancestors2;
3932 ancestors2.push_back(&second);
3933 while (ancestors2.back())
3934 ancestors2.push_back(ancestors2.back()->ParentObjectIncludedInTree());
3935
3936 const AXObject* common_ancestor = nullptr;
3937 while (!ancestors1.IsEmpty() && !ancestors2.IsEmpty() &&
3938 ancestors1.back() == ancestors2.back()) {
3939 common_ancestor = ancestors1.back();
3940 ancestors1.pop_back();
3941 ancestors2.pop_back();
3942 }
3943
3944 if (common_ancestor) {
3945 if (!ancestors1.IsEmpty())
3946 *index_in_ancestor1 = ancestors1.back()->IndexInParent();
3947 if (!ancestors2.IsEmpty())
3948 *index_in_ancestor2 = ancestors2.back()->IndexInParent();
3949 }
3950
3951 return common_ancestor;
3952 }
3953
ToString() const3954 String AXObject::ToString() const {
3955 return AXObject::InternalRoleName(RoleValue())
3956 .GetString()
3957 .EncodeForDebugging() +
3958 ": " + ComputedName().EncodeForDebugging();
3959 }
3960
operator ==(const AXObject & first,const AXObject & second)3961 bool operator==(const AXObject& first, const AXObject& second) {
3962 if (first.IsDetached() || second.IsDetached())
3963 return false;
3964 if (&first == &second) {
3965 DCHECK_EQ(first.AXObjectID(), second.AXObjectID());
3966 return true;
3967 }
3968 return false;
3969 }
3970
operator !=(const AXObject & first,const AXObject & second)3971 bool operator!=(const AXObject& first, const AXObject& second) {
3972 return !(first == second);
3973 }
3974
operator <(const AXObject & first,const AXObject & second)3975 bool operator<(const AXObject& first, const AXObject& second) {
3976 if (first.IsDetached() || second.IsDetached())
3977 return false;
3978
3979 int index_in_ancestor1, index_in_ancestor2;
3980 const AXObject* ancestor = AXObject::LowestCommonAncestor(
3981 first, second, &index_in_ancestor1, &index_in_ancestor2);
3982 DCHECK_GE(index_in_ancestor1, -1);
3983 DCHECK_GE(index_in_ancestor2, -1);
3984 if (!ancestor)
3985 return false;
3986 return index_in_ancestor1 < index_in_ancestor2;
3987 }
3988
operator <=(const AXObject & first,const AXObject & second)3989 bool operator<=(const AXObject& first, const AXObject& second) {
3990 return first == second || first < second;
3991 }
3992
operator >(const AXObject & first,const AXObject & second)3993 bool operator>(const AXObject& first, const AXObject& second) {
3994 if (first.IsDetached() || second.IsDetached())
3995 return false;
3996
3997 int index_in_ancestor1, index_in_ancestor2;
3998 const AXObject* ancestor = AXObject::LowestCommonAncestor(
3999 first, second, &index_in_ancestor1, &index_in_ancestor2);
4000 DCHECK_GE(index_in_ancestor1, -1);
4001 DCHECK_GE(index_in_ancestor2, -1);
4002 if (!ancestor)
4003 return false;
4004 return index_in_ancestor1 > index_in_ancestor2;
4005 }
4006
operator >=(const AXObject & first,const AXObject & second)4007 bool operator>=(const AXObject& first, const AXObject& second) {
4008 return first == second || first > second;
4009 }
4010
operator <<(std::ostream & stream,const AXObject & obj)4011 std::ostream& operator<<(std::ostream& stream, const AXObject& obj) {
4012 return stream << obj.ToString().Utf8();
4013 }
4014
Trace(Visitor * visitor)4015 void AXObject::Trace(Visitor* visitor) {
4016 visitor->Trace(children_);
4017 visitor->Trace(parent_);
4018 visitor->Trace(cached_live_region_root_);
4019 visitor->Trace(ax_object_cache_);
4020 }
4021
4022 } // namespace blink
4023