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