Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(455)

Side by Side Diff: third_party/WebKit/Source/modules/accessibility/AXObject.cpp

Issue 2858493002: Rename AXObject to AXObjectImpl in modules/ and web/ (Closed)
Patch Set: Fixed rebase Created 3 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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 "modules/accessibility/AXObject.h"
30
31 #include "SkMatrix44.h"
32 #include "core/InputTypeNames.h"
33 #include "core/css/resolver/StyleResolver.h"
34 #include "core/dom/AccessibleNode.h"
35 #include "core/dom/DocumentUserGestureToken.h"
36 #include "core/editing/EditingUtilities.h"
37 #include "core/editing/VisibleUnits.h"
38 #include "core/frame/FrameView.h"
39 #include "core/frame/LocalFrame.h"
40 #include "core/frame/Settings.h"
41 #include "core/html/HTMLDialogElement.h"
42 #include "core/html/HTMLFrameOwnerElement.h"
43 #include "core/html/HTMLInputElement.h"
44 #include "core/html/parser/HTMLParserIdioms.h"
45 #include "core/layout/LayoutBoxModelObject.h"
46 #include "modules/accessibility/AXObjectCacheImpl.h"
47 #include "platform/UserGestureIndicator.h"
48 #include "platform/text/PlatformLocale.h"
49 #include "platform/wtf/HashSet.h"
50 #include "platform/wtf/StdLibExtras.h"
51 #include "platform/wtf/text/WTFString.h"
52
53 using blink::WebLocalizedString;
54
55 namespace blink {
56
57 using namespace HTMLNames;
58
59 namespace {
60 typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
61 typedef HashSet<String, CaseFoldingHash> ARIAWidgetSet;
62
63 struct RoleEntry {
64 const char* aria_role;
65 AccessibilityRole webcore_role;
66 };
67
68 const RoleEntry kRoles[] = {{"alert", kAlertRole},
69 {"alertdialog", kAlertDialogRole},
70 {"application", kApplicationRole},
71 {"article", kArticleRole},
72 {"banner", kBannerRole},
73 {"button", kButtonRole},
74 {"cell", kCellRole},
75 {"checkbox", kCheckBoxRole},
76 {"columnheader", kColumnHeaderRole},
77 {"combobox", kComboBoxRole},
78 {"complementary", kComplementaryRole},
79 {"contentinfo", kContentInfoRole},
80 {"definition", kDefinitionRole},
81 {"dialog", kDialogRole},
82 {"directory", kDirectoryRole},
83 {"document", kDocumentRole},
84 {"feed", kFeedRole},
85 {"figure", kFigureRole},
86 {"form", kFormRole},
87 {"grid", kGridRole},
88 {"gridcell", kCellRole},
89 {"group", kGroupRole},
90 {"heading", kHeadingRole},
91 {"img", kImageRole},
92 {"link", kLinkRole},
93 {"list", kListRole},
94 {"listbox", kListBoxRole},
95 {"listitem", kListItemRole},
96 {"log", kLogRole},
97 {"main", kMainRole},
98 {"marquee", kMarqueeRole},
99 {"math", kMathRole},
100 {"menu", kMenuRole},
101 {"menubar", kMenuBarRole},
102 {"menuitem", kMenuItemRole},
103 {"menuitemcheckbox", kMenuItemCheckBoxRole},
104 {"menuitemradio", kMenuItemRadioRole},
105 {"navigation", kNavigationRole},
106 {"none", kNoneRole},
107 {"note", kNoteRole},
108 {"option", kListBoxOptionRole},
109 {"presentation", kPresentationalRole},
110 {"progressbar", kProgressIndicatorRole},
111 {"radio", kRadioButtonRole},
112 {"radiogroup", kRadioGroupRole},
113 {"region", kRegionRole},
114 {"row", kRowRole},
115 {"rowheader", kRowHeaderRole},
116 {"scrollbar", kScrollBarRole},
117 {"search", kSearchRole},
118 {"searchbox", kSearchBoxRole},
119 {"separator", kSplitterRole},
120 {"slider", kSliderRole},
121 {"spinbutton", kSpinButtonRole},
122 {"status", kStatusRole},
123 {"switch", kSwitchRole},
124 {"tab", kTabRole},
125 {"table", kTableRole},
126 {"tablist", kTabListRole},
127 {"tabpanel", kTabPanelRole},
128 {"term", kTermRole},
129 {"text", kStaticTextRole},
130 {"textbox", kTextFieldRole},
131 {"timer", kTimerRole},
132 {"toolbar", kToolbarRole},
133 {"tooltip", kUserInterfaceTooltipRole},
134 {"tree", kTreeRole},
135 {"treegrid", kTreeGridRole},
136 {"treeitem", kTreeItemRole}};
137
138 struct InternalRoleEntry {
139 AccessibilityRole webcore_role;
140 const char* internal_role_name;
141 };
142
143 const InternalRoleEntry kInternalRoles[] = {
144 {kUnknownRole, "Unknown"},
145 {kAbbrRole, "Abbr"},
146 {kAlertDialogRole, "AlertDialog"},
147 {kAlertRole, "Alert"},
148 {kAnchorRole, "Anchor"},
149 {kAnnotationRole, "Annotation"},
150 {kApplicationRole, "Application"},
151 {kArticleRole, "Article"},
152 {kAudioRole, "Audio"},
153 {kBannerRole, "Banner"},
154 {kBlockquoteRole, "Blockquote"},
155 // TODO(nektar): Delete busy_indicator role. It's used nowhere.
156 {kBusyIndicatorRole, "BusyIndicator"},
157 {kButtonRole, "Button"},
158 {kCanvasRole, "Canvas"},
159 {kCaptionRole, "Caption"},
160 {kCellRole, "Cell"},
161 {kCheckBoxRole, "CheckBox"},
162 {kColorWellRole, "ColorWell"},
163 {kColumnHeaderRole, "ColumnHeader"},
164 {kColumnRole, "Column"},
165 {kComboBoxRole, "ComboBox"},
166 {kComplementaryRole, "Complementary"},
167 {kContentInfoRole, "ContentInfo"},
168 {kDateRole, "Date"},
169 {kDateTimeRole, "DateTime"},
170 {kDefinitionRole, "Definition"},
171 {kDescriptionListDetailRole, "DescriptionListDetail"},
172 {kDescriptionListRole, "DescriptionList"},
173 {kDescriptionListTermRole, "DescriptionListTerm"},
174 {kDetailsRole, "Details"},
175 {kDialogRole, "Dialog"},
176 {kDirectoryRole, "Directory"},
177 {kDisclosureTriangleRole, "DisclosureTriangle"},
178 {kDivRole, "Div"},
179 {kDocumentRole, "Document"},
180 {kEmbeddedObjectRole, "EmbeddedObject"},
181 {kFeedRole, "feed"},
182 {kFigcaptionRole, "Figcaption"},
183 {kFigureRole, "Figure"},
184 {kFooterRole, "Footer"},
185 {kFormRole, "Form"},
186 {kGridRole, "Grid"},
187 {kGroupRole, "Group"},
188 {kHeadingRole, "Heading"},
189 {kIframePresentationalRole, "IframePresentational"},
190 {kIframeRole, "Iframe"},
191 {kIgnoredRole, "Ignored"},
192 {kImageMapLinkRole, "ImageMapLink"},
193 {kImageMapRole, "ImageMap"},
194 {kImageRole, "Image"},
195 {kInlineTextBoxRole, "InlineTextBox"},
196 {kInputTimeRole, "InputTime"},
197 {kLabelRole, "Label"},
198 {kLegendRole, "Legend"},
199 {kLinkRole, "Link"},
200 {kLineBreakRole, "LineBreak"},
201 {kListBoxOptionRole, "ListBoxOption"},
202 {kListBoxRole, "ListBox"},
203 {kListItemRole, "ListItem"},
204 {kListMarkerRole, "ListMarker"},
205 {kListRole, "List"},
206 {kLogRole, "Log"},
207 {kMainRole, "Main"},
208 {kMarkRole, "Mark"},
209 {kMarqueeRole, "Marquee"},
210 {kMathRole, "Math"},
211 {kMenuBarRole, "MenuBar"},
212 {kMenuButtonRole, "MenuButton"},
213 {kMenuItemRole, "MenuItem"},
214 {kMenuItemCheckBoxRole, "MenuItemCheckBox"},
215 {kMenuItemRadioRole, "MenuItemRadio"},
216 {kMenuListOptionRole, "MenuListOption"},
217 {kMenuListPopupRole, "MenuListPopup"},
218 {kMenuRole, "Menu"},
219 {kMeterRole, "Meter"},
220 {kNavigationRole, "Navigation"},
221 {kNoneRole, "None"},
222 {kNoteRole, "Note"},
223 {kOutlineRole, "Outline"},
224 {kParagraphRole, "Paragraph"},
225 {kPopUpButtonRole, "PopUpButton"},
226 {kPreRole, "Pre"},
227 {kPresentationalRole, "Presentational"},
228 {kProgressIndicatorRole, "ProgressIndicator"},
229 {kRadioButtonRole, "RadioButton"},
230 {kRadioGroupRole, "RadioGroup"},
231 {kRegionRole, "Region"},
232 {kRootWebAreaRole, "RootWebArea"},
233 {kRowHeaderRole, "RowHeader"},
234 {kRowRole, "Row"},
235 {kRubyRole, "Ruby"},
236 {kRulerRole, "Ruler"},
237 {kSVGRootRole, "SVGRoot"},
238 {kScrollAreaRole, "ScrollArea"},
239 {kScrollBarRole, "ScrollBar"},
240 {kSeamlessWebAreaRole, "SeamlessWebArea"},
241 {kSearchRole, "Search"},
242 {kSearchBoxRole, "SearchBox"},
243 {kSliderRole, "Slider"},
244 {kSliderThumbRole, "SliderThumb"},
245 {kSpinButtonPartRole, "SpinButtonPart"},
246 {kSpinButtonRole, "SpinButton"},
247 {kSplitterRole, "Splitter"},
248 {kStaticTextRole, "StaticText"},
249 {kStatusRole, "Status"},
250 {kSwitchRole, "Switch"},
251 {kTabGroupRole, "TabGroup"},
252 {kTabListRole, "TabList"},
253 {kTabPanelRole, "TabPanel"},
254 {kTabRole, "Tab"},
255 {kTableHeaderContainerRole, "TableHeaderContainer"},
256 {kTableRole, "Table"},
257 {kTermRole, "Term"},
258 {kTextFieldRole, "TextField"},
259 {kTimeRole, "Time"},
260 {kTimerRole, "Timer"},
261 {kToggleButtonRole, "ToggleButton"},
262 {kToolbarRole, "Toolbar"},
263 {kTreeGridRole, "TreeGrid"},
264 {kTreeItemRole, "TreeItem"},
265 {kTreeRole, "Tree"},
266 {kUserInterfaceTooltipRole, "UserInterfaceTooltip"},
267 {kVideoRole, "Video"},
268 {kWebAreaRole, "WebArea"},
269 {kWindowRole, "Window"}};
270
271 static_assert(WTF_ARRAY_LENGTH(kInternalRoles) == kNumRoles,
272 "Not all internal roles have an entry in internalRoles array");
273
274 // Roles which we need to map in the other direction
275 const RoleEntry kReverseRoles[] = {
276 {"button", kToggleButtonRole}, {"combobox", kPopUpButtonRole},
277 {"contentinfo", kFooterRole}, {"menuitem", kMenuButtonRole},
278 {"menuitem", kMenuListOptionRole}, {"progressbar", kMeterRole},
279 {"textbox", kTextFieldRole}};
280
281 static ARIARoleMap* CreateARIARoleMap() {
282 ARIARoleMap* role_map = new ARIARoleMap;
283
284 for (size_t i = 0; i < WTF_ARRAY_LENGTH(kRoles); ++i)
285 role_map->Set(String(kRoles[i].aria_role), kRoles[i].webcore_role);
286 return role_map;
287 }
288
289 static Vector<AtomicString>* CreateRoleNameVector() {
290 Vector<AtomicString>* role_name_vector = new Vector<AtomicString>(kNumRoles);
291 for (int i = 0; i < kNumRoles; i++)
292 (*role_name_vector)[i] = g_null_atom;
293
294 for (size_t i = 0; i < WTF_ARRAY_LENGTH(kRoles); ++i)
295 (*role_name_vector)[kRoles[i].webcore_role] =
296 AtomicString(kRoles[i].aria_role);
297
298 for (size_t i = 0; i < WTF_ARRAY_LENGTH(kReverseRoles); ++i)
299 (*role_name_vector)[kReverseRoles[i].webcore_role] =
300 AtomicString(kReverseRoles[i].aria_role);
301
302 return role_name_vector;
303 }
304
305 static Vector<AtomicString>* CreateInternalRoleNameVector() {
306 Vector<AtomicString>* internal_role_name_vector =
307 new Vector<AtomicString>(kNumRoles);
308 for (size_t i = 0; i < WTF_ARRAY_LENGTH(kInternalRoles); i++)
309 (*internal_role_name_vector)[kInternalRoles[i].webcore_role] =
310 AtomicString(kInternalRoles[i].internal_role_name);
311
312 return internal_role_name_vector;
313 }
314
315 const char* g_aria_widgets[] = {
316 // From http://www.w3.org/TR/wai-aria/roles#widget_roles
317 "alert", "alertdialog", "button", "checkbox", "dialog", "gridcell", "link",
318 "log", "marquee", "menuitem", "menuitemcheckbox", "menuitemradio", "option",
319 "progressbar", "radio", "scrollbar", "slider", "spinbutton", "status",
320 "tab", "tabpanel", "textbox", "timer", "tooltip", "treeitem",
321 // Composite user interface widgets.
322 // This list is also from the w3.org site referenced above.
323 "combobox", "grid", "listbox", "menu", "menubar", "radiogroup", "tablist",
324 "tree", "treegrid"};
325
326 static ARIAWidgetSet* CreateARIARoleWidgetSet() {
327 ARIAWidgetSet* widget_set = new HashSet<String, CaseFoldingHash>();
328 for (size_t i = 0; i < WTF_ARRAY_LENGTH(g_aria_widgets); ++i)
329 widget_set->insert(String(g_aria_widgets[i]));
330 return widget_set;
331 }
332
333 const char* g_aria_interactive_widget_attributes[] = {
334 // These attributes implicitly indicate the given widget is interactive.
335 // From http://www.w3.org/TR/wai-aria/states_and_properties#attrs_widgets
336 "aria-activedescendant", "aria-checked", "aria-controls",
337 "aria-disabled", // If it's disabled, it can be made interactive.
338 "aria-expanded", "aria-haspopup", "aria-multiselectable",
339 "aria-pressed", "aria-required", "aria-selected"};
340
341 HTMLDialogElement* GetActiveDialogElement(Node* node) {
342 return node->GetDocument().ActiveModalDialog();
343 }
344
345 } // namespace
346
347 unsigned AXObject::number_of_live_ax_objects_ = 0;
348
349 AXObject::AXObject(AXObjectCacheImpl& ax_object_cache)
350 : id_(0),
351 have_children_(false),
352 role_(kUnknownRole),
353 last_known_is_ignored_value_(kDefaultBehavior),
354 explicit_container_id_(0),
355 parent_(nullptr),
356 last_modification_count_(-1),
357 cached_is_ignored_(false),
358 cached_is_inert_or_aria_hidden_(false),
359 cached_is_descendant_of_leaf_node_(false),
360 cached_is_descendant_of_disabled_node_(false),
361 cached_has_inherited_presentational_role_(false),
362 cached_is_presentational_child_(false),
363 cached_ancestor_exposes_active_descendant_(false),
364 cached_live_region_root_(nullptr),
365 ax_object_cache_(&ax_object_cache) {
366 ++number_of_live_ax_objects_;
367 }
368
369 AXObject::~AXObject() {
370 DCHECK(IsDetached());
371 --number_of_live_ax_objects_;
372 }
373
374 void AXObject::Detach() {
375 // Clear any children and call detachFromParent on them so that
376 // no children are left with dangling pointers to their parent.
377 ClearChildren();
378
379 ax_object_cache_ = nullptr;
380 }
381
382 bool AXObject::IsDetached() const {
383 return !ax_object_cache_;
384 }
385
386 const AtomicString& AXObject::GetAOMPropertyOrARIAAttribute(
387 AOMStringProperty property) const {
388 Node* node = this->GetNode();
389 if (!node || !node->IsElementNode())
390 return g_null_atom;
391
392 return AccessibleNode::GetPropertyOrARIAAttribute(ToElement(node), property);
393 }
394
395 bool AXObject::IsARIATextControl() const {
396 return AriaRoleAttribute() == kTextFieldRole ||
397 AriaRoleAttribute() == kSearchBoxRole ||
398 AriaRoleAttribute() == kComboBoxRole;
399 }
400
401 bool AXObject::IsButton() const {
402 AccessibilityRole role = RoleValue();
403
404 return role == kButtonRole || role == kPopUpButtonRole ||
405 role == kToggleButtonRole;
406 }
407
408 bool AXObject::IsCheckable() const {
409 switch (RoleValue()) {
410 case kCheckBoxRole:
411 case kMenuItemCheckBoxRole:
412 case kMenuItemRadioRole:
413 case kRadioButtonRole:
414 case kSwitchRole:
415 return true;
416 default:
417 return false;
418 }
419 }
420
421 // Why this is here instead of AXNodeObject:
422 // Because an AXMenuListOption (<option>) can
423 // have an ARIA role of menuitemcheckbox/menuitemradio
424 // yet does not inherit from AXNodeObject
425 AccessibilityButtonState AXObject::CheckedState() const {
426 if (!IsCheckable())
427 return kButtonStateOff;
428
429 const AtomicString& checkedAttribute =
430 GetAOMPropertyOrARIAAttribute(AOMStringProperty::kChecked);
431 if (checkedAttribute) {
432 if (EqualIgnoringASCIICase(checkedAttribute, "true"))
433 return kButtonStateOn;
434
435 if (EqualIgnoringASCIICase(checkedAttribute, "mixed")) {
436 // Only checkboxes and radios should support the mixed state.
437 const AccessibilityRole role = RoleValue();
438 if (role == kCheckBoxRole || role == kMenuItemCheckBoxRole ||
439 role == kRadioButtonRole || role == kMenuItemRadioRole)
440 return kButtonStateMixed;
441 }
442
443 return kButtonStateOff;
444 }
445
446 const Node* node = this->GetNode();
447 if (!node)
448 return kButtonStateOff;
449
450 if (IsNativeInputInMixedState(node))
451 return kButtonStateMixed;
452
453 if (isHTMLInputElement(*node) &&
454 toHTMLInputElement(*node).ShouldAppearChecked()) {
455 return kButtonStateOn;
456 }
457
458 return kButtonStateOff;
459 }
460
461 bool AXObject::IsNativeInputInMixedState(const Node* node) {
462 if (!isHTMLInputElement(node))
463 return false;
464
465 const HTMLInputElement* input = toHTMLInputElement(node);
466 const auto inputType = input->type();
467 if (inputType != InputTypeNames::checkbox &&
468 inputType != InputTypeNames::radio)
469 return false;
470 return input->ShouldAppearIndeterminate();
471 }
472
473 bool AXObject::IsLandmarkRelated() const {
474 switch (RoleValue()) {
475 case kApplicationRole:
476 case kArticleRole:
477 case kBannerRole:
478 case kComplementaryRole:
479 case kContentInfoRole:
480 case kFooterRole:
481 case kFormRole:
482 case kMainRole:
483 case kNavigationRole:
484 case kRegionRole:
485 case kSearchRole:
486 return true;
487 default:
488 return false;
489 }
490 }
491
492 bool AXObject::IsMenuRelated() const {
493 switch (RoleValue()) {
494 case kMenuRole:
495 case kMenuBarRole:
496 case kMenuButtonRole:
497 case kMenuItemRole:
498 case kMenuItemCheckBoxRole:
499 case kMenuItemRadioRole:
500 return true;
501 default:
502 return false;
503 }
504 }
505
506 bool AXObject::IsPasswordFieldAndShouldHideValue() const {
507 Settings* settings = GetDocument()->GetSettings();
508 if (!settings || settings->GetAccessibilityPasswordValuesEnabled())
509 return false;
510
511 return IsPasswordField();
512 }
513
514 bool AXObject::IsClickable() const {
515 switch (RoleValue()) {
516 case kButtonRole:
517 case kCheckBoxRole:
518 case kColorWellRole:
519 case kComboBoxRole:
520 case kImageMapLinkRole:
521 case kLinkRole:
522 case kListBoxOptionRole:
523 case kMenuButtonRole:
524 case kPopUpButtonRole:
525 case kRadioButtonRole:
526 case kSpinButtonRole:
527 case kTabRole:
528 case kTextFieldRole:
529 case kToggleButtonRole:
530 return true;
531 default:
532 return false;
533 }
534 }
535
536 bool AXObject::AccessibilityIsIgnored() const {
537 UpdateCachedAttributeValuesIfNeeded();
538 return cached_is_ignored_;
539 }
540
541 void AXObject::UpdateCachedAttributeValuesIfNeeded() const {
542 if (IsDetached())
543 return;
544
545 AXObjectCacheImpl& cache = AxObjectCache();
546
547 if (cache.ModificationCount() == last_modification_count_)
548 return;
549
550 last_modification_count_ = cache.ModificationCount();
551 cached_background_color_ = ComputeBackgroundColor();
552 cached_is_inert_or_aria_hidden_ = ComputeIsInertOrAriaHidden();
553 cached_is_descendant_of_leaf_node_ = (LeafNodeAncestor() != 0);
554 cached_is_descendant_of_disabled_node_ = (DisabledAncestor() != 0);
555 cached_has_inherited_presentational_role_ =
556 (InheritsPresentationalRoleFrom() != 0);
557 cached_is_presentational_child_ =
558 (AncestorForWhichThisIsAPresentationalChild() != 0);
559 cached_is_ignored_ = ComputeAccessibilityIsIgnored();
560 cached_live_region_root_ =
561 IsLiveRegion()
562 ? const_cast<AXObject*>(this)
563 : (ParentObjectIfExists() ? ParentObjectIfExists()->LiveRegionRoot()
564 : 0);
565 cached_ancestor_exposes_active_descendant_ =
566 ComputeAncestorExposesActiveDescendant();
567 }
568
569 bool AXObject::AccessibilityIsIgnoredByDefault(
570 IgnoredReasons* ignored_reasons) const {
571 return DefaultObjectInclusion(ignored_reasons) == kIgnoreObject;
572 }
573
574 AXObjectInclusion AXObject::AccessibilityPlatformIncludesObject() const {
575 if (IsMenuListPopup() || IsMenuListOption())
576 return kIncludeObject;
577
578 return kDefaultBehavior;
579 }
580
581 AXObjectInclusion AXObject::DefaultObjectInclusion(
582 IgnoredReasons* ignored_reasons) const {
583 if (IsInertOrAriaHidden()) {
584 if (ignored_reasons)
585 ComputeIsInertOrAriaHidden(ignored_reasons);
586 return kIgnoreObject;
587 }
588
589 if (IsPresentationalChild()) {
590 if (ignored_reasons) {
591 AXObject* ancestor = AncestorForWhichThisIsAPresentationalChild();
592 ignored_reasons->push_back(
593 IgnoredReason(kAXAncestorDisallowsChild, ancestor));
594 }
595 return kIgnoreObject;
596 }
597
598 return AccessibilityPlatformIncludesObject();
599 }
600
601 bool AXObject::IsInertOrAriaHidden() const {
602 UpdateCachedAttributeValuesIfNeeded();
603 return cached_is_inert_or_aria_hidden_;
604 }
605
606 bool AXObject::ComputeIsInertOrAriaHidden(
607 IgnoredReasons* ignored_reasons) const {
608 if (GetNode()) {
609 if (GetNode()->IsInert()) {
610 if (ignored_reasons) {
611 HTMLDialogElement* dialog = GetActiveDialogElement(GetNode());
612 if (dialog) {
613 AXObject* dialog_object = AxObjectCache().GetOrCreate(dialog);
614 if (dialog_object)
615 ignored_reasons->push_back(
616 IgnoredReason(kAXActiveModalDialog, dialog_object));
617 else
618 ignored_reasons->push_back(IgnoredReason(kAXInert));
619 } else {
620 // TODO(aboxhall): handle inert attribute if it eventuates
621 ignored_reasons->push_back(IgnoredReason(kAXInert));
622 }
623 }
624 return true;
625 }
626 } else {
627 AXObject* parent = ParentObject();
628 if (parent && parent->IsInertOrAriaHidden()) {
629 if (ignored_reasons)
630 parent->ComputeIsInertOrAriaHidden(ignored_reasons);
631 return true;
632 }
633 }
634
635 const AXObject* hidden_root = AriaHiddenRoot();
636 if (hidden_root) {
637 if (ignored_reasons) {
638 if (hidden_root == this)
639 ignored_reasons->push_back(IgnoredReason(kAXAriaHidden));
640 else
641 ignored_reasons->push_back(
642 IgnoredReason(kAXAriaHiddenRoot, hidden_root));
643 }
644 return true;
645 }
646
647 return false;
648 }
649
650 bool AXObject::IsDescendantOfLeafNode() const {
651 UpdateCachedAttributeValuesIfNeeded();
652 return cached_is_descendant_of_leaf_node_;
653 }
654
655 AXObject* AXObject::LeafNodeAncestor() const {
656 if (AXObject* parent = ParentObject()) {
657 if (!parent->CanHaveChildren())
658 return parent;
659
660 return parent->LeafNodeAncestor();
661 }
662
663 return 0;
664 }
665
666 const AXObject* AXObject::AriaHiddenRoot() const {
667 for (const AXObject* object = this; object; object = object->ParentObject()) {
668 if (EqualIgnoringASCIICase(object->GetAttribute(aria_hiddenAttr), "true"))
669 return object;
670 }
671
672 return 0;
673 }
674
675 bool AXObject::IsDescendantOfDisabledNode() const {
676 UpdateCachedAttributeValuesIfNeeded();
677 return cached_is_descendant_of_disabled_node_;
678 }
679
680 const AXObject* AXObject::DisabledAncestor() const {
681 const AtomicString& disabled = GetAttribute(aria_disabledAttr);
682 if (EqualIgnoringASCIICase(disabled, "true"))
683 return this;
684 if (EqualIgnoringASCIICase(disabled, "false"))
685 return 0;
686
687 if (AXObject* parent = ParentObject())
688 return parent->DisabledAncestor();
689
690 return 0;
691 }
692
693 bool AXObject::LastKnownIsIgnoredValue() {
694 if (last_known_is_ignored_value_ == kDefaultBehavior)
695 last_known_is_ignored_value_ =
696 AccessibilityIsIgnored() ? kIgnoreObject : kIncludeObject;
697
698 return last_known_is_ignored_value_ == kIgnoreObject;
699 }
700
701 void AXObject::SetLastKnownIsIgnoredValue(bool is_ignored) {
702 last_known_is_ignored_value_ = is_ignored ? kIgnoreObject : kIncludeObject;
703 }
704
705 bool AXObject::HasInheritedPresentationalRole() const {
706 UpdateCachedAttributeValuesIfNeeded();
707 return cached_has_inherited_presentational_role_;
708 }
709
710 bool AXObject::IsPresentationalChild() const {
711 UpdateCachedAttributeValuesIfNeeded();
712 return cached_is_presentational_child_;
713 }
714
715 bool AXObject::AncestorExposesActiveDescendant() const {
716 UpdateCachedAttributeValuesIfNeeded();
717 return cached_ancestor_exposes_active_descendant_;
718 }
719
720 bool AXObject::ComputeAncestorExposesActiveDescendant() const {
721 const AXObject* parent = ParentObjectUnignored();
722 if (!parent)
723 return false;
724
725 if (parent->SupportsActiveDescendant() &&
726 !parent->GetAttribute(aria_activedescendantAttr).IsEmpty()) {
727 return true;
728 }
729
730 return parent->AncestorExposesActiveDescendant();
731 }
732
733 // Simplify whitespace, but preserve a single leading and trailing whitespace
734 // character if it's present.
735 // static
736 String AXObject::CollapseWhitespace(const String& str) {
737 StringBuilder result;
738 if (!str.IsEmpty() && IsHTMLSpace<UChar>(str[0]))
739 result.Append(' ');
740 result.Append(str.SimplifyWhiteSpace(IsHTMLSpace<UChar>));
741 if (!str.IsEmpty() && IsHTMLSpace<UChar>(str[str.length() - 1]))
742 result.Append(' ');
743 return result.ToString();
744 }
745
746 String AXObject::ComputedName() const {
747 AXNameFrom name_from;
748 AXObject::AXObjectVector name_objects;
749 return GetName(name_from, &name_objects);
750 }
751
752 String AXObject::GetName(AXNameFrom& name_from,
753 AXObject::AXObjectVector* name_objects) const {
754 HeapHashSet<Member<const AXObject>> visited;
755 AXRelatedObjectVector related_objects;
756 String text = TextAlternative(false, false, visited, name_from,
757 &related_objects, nullptr);
758
759 AccessibilityRole role = RoleValue();
760 if (!GetNode() || (!isHTMLBRElement(GetNode()) && role != kStaticTextRole &&
761 role != kInlineTextBoxRole))
762 text = CollapseWhitespace(text);
763
764 if (name_objects) {
765 name_objects->clear();
766 for (size_t i = 0; i < related_objects.size(); i++)
767 name_objects->push_back(related_objects[i]->object);
768 }
769
770 return text;
771 }
772
773 String AXObject::GetName(NameSources* name_sources) const {
774 AXObjectSet visited;
775 AXNameFrom tmp_name_from;
776 AXRelatedObjectVector tmp_related_objects;
777 String text = TextAlternative(false, false, visited, tmp_name_from,
778 &tmp_related_objects, name_sources);
779 text = text.SimplifyWhiteSpace(IsHTMLSpace<UChar>);
780 return text;
781 }
782
783 String AXObject::RecursiveTextAlternative(const AXObject& ax_obj,
784 bool in_aria_labelled_by_traversal,
785 AXObjectSet& visited) {
786 if (visited.Contains(&ax_obj) && !in_aria_labelled_by_traversal)
787 return String();
788
789 AXNameFrom tmp_name_from;
790 return ax_obj.TextAlternative(true, in_aria_labelled_by_traversal, visited,
791 tmp_name_from, nullptr, nullptr);
792 }
793
794 bool AXObject::IsHiddenForTextAlternativeCalculation() const {
795 if (EqualIgnoringASCIICase(GetAttribute(aria_hiddenAttr), "false"))
796 return false;
797
798 if (GetLayoutObject())
799 return GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible;
800
801 // This is an obscure corner case: if a node has no LayoutObject, that means
802 // it's not rendered, but we still may be exploring it as part of a text
803 // alternative calculation, for example if it was explicitly referenced by
804 // aria-labelledby. So we need to explicitly call the style resolver to check
805 // whether it's invisible or display:none, rather than relying on the style
806 // cached in the LayoutObject.
807 Document* document = GetDocument();
808 if (!document || !document->GetFrame())
809 return false;
810 if (Node* node = GetNode()) {
811 if (node->isConnected() && node->IsElementNode()) {
812 RefPtr<ComputedStyle> style =
813 document->EnsureStyleResolver().StyleForElement(ToElement(node));
814 return style->Display() == EDisplay::kNone ||
815 style->Visibility() != EVisibility::kVisible;
816 }
817 }
818 return false;
819 }
820
821 String AXObject::AriaTextAlternative(bool recursive,
822 bool in_aria_labelled_by_traversal,
823 AXObjectSet& visited,
824 AXNameFrom& name_from,
825 AXRelatedObjectVector* related_objects,
826 NameSources* name_sources,
827 bool* found_text_alternative) const {
828 String text_alternative;
829 bool already_visited = visited.Contains(this);
830 visited.insert(this);
831
832 // Step 2A from: http://www.w3.org/TR/accname-aam-1.1
833 // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
834 if (!in_aria_labelled_by_traversal &&
835 IsHiddenForTextAlternativeCalculation()) {
836 *found_text_alternative = true;
837 return String();
838 }
839
840 // Step 2B from: http://www.w3.org/TR/accname-aam-1.1
841 // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
842 if (!in_aria_labelled_by_traversal && !already_visited) {
843 const QualifiedName& attr =
844 HasAttribute(aria_labeledbyAttr) && !HasAttribute(aria_labelledbyAttr)
845 ? aria_labeledbyAttr
846 : aria_labelledbyAttr;
847 name_from = kAXNameFromRelatedElement;
848 if (name_sources) {
849 name_sources->push_back(NameSource(*found_text_alternative, attr));
850 name_sources->back().type = name_from;
851 }
852
853 const AtomicString& aria_labelledby = GetAttribute(attr);
854 if (!aria_labelledby.IsNull()) {
855 if (name_sources)
856 name_sources->back().attribute_value = aria_labelledby;
857
858 // Operate on a copy of |visited| so that if |nameSources| is not null,
859 // the set of visited objects is preserved unmodified for future
860 // calculations.
861 AXObjectSet visited_copy = visited;
862 text_alternative = TextFromAriaLabelledby(visited_copy, related_objects);
863 if (!text_alternative.IsNull()) {
864 if (name_sources) {
865 NameSource& source = name_sources->back();
866 source.type = name_from;
867 source.related_objects = *related_objects;
868 source.text = text_alternative;
869 *found_text_alternative = true;
870 } else {
871 *found_text_alternative = true;
872 return text_alternative;
873 }
874 } else if (name_sources) {
875 name_sources->back().invalid = true;
876 }
877 }
878 }
879
880 // Step 2C from: http://www.w3.org/TR/accname-aam-1.1
881 // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
882 name_from = kAXNameFromAttribute;
883 if (name_sources) {
884 name_sources->push_back(
885 NameSource(*found_text_alternative, aria_labelAttr));
886 name_sources->back().type = name_from;
887 }
888 const AtomicString& aria_label =
889 GetAOMPropertyOrARIAAttribute(AOMStringProperty::kLabel);
890 if (!aria_label.IsEmpty()) {
891 text_alternative = aria_label;
892
893 if (name_sources) {
894 NameSource& source = name_sources->back();
895 source.text = text_alternative;
896 source.attribute_value = aria_label;
897 *found_text_alternative = true;
898 } else {
899 *found_text_alternative = true;
900 return text_alternative;
901 }
902 }
903
904 return text_alternative;
905 }
906
907 String AXObject::TextFromElements(
908 bool in_aria_labelledby_traversal,
909 AXObjectSet& visited,
910 HeapVector<Member<Element>>& elements,
911 AXRelatedObjectVector* related_objects) const {
912 StringBuilder accumulated_text;
913 bool found_valid_element = false;
914 AXRelatedObjectVector local_related_objects;
915
916 for (const auto& element : elements) {
917 AXObject* ax_element = AxObjectCache().GetOrCreate(element);
918 if (ax_element) {
919 found_valid_element = true;
920
921 String result = RecursiveTextAlternative(
922 *ax_element, in_aria_labelledby_traversal, visited);
923 local_related_objects.push_back(
924 new NameSourceRelatedObject(ax_element, result));
925 if (!result.IsEmpty()) {
926 if (!accumulated_text.IsEmpty())
927 accumulated_text.Append(' ');
928 accumulated_text.Append(result);
929 }
930 }
931 }
932 if (!found_valid_element)
933 return String();
934 if (related_objects)
935 *related_objects = local_related_objects;
936 return accumulated_text.ToString();
937 }
938
939 void AXObject::TokenVectorFromAttribute(Vector<String>& tokens,
940 const QualifiedName& attribute) const {
941 Node* node = this->GetNode();
942 if (!node || !node->IsElementNode())
943 return;
944
945 String attribute_value = GetAttribute(attribute).GetString();
946 if (attribute_value.IsEmpty())
947 return;
948
949 attribute_value.SimplifyWhiteSpace();
950 attribute_value.Split(' ', tokens);
951 }
952
953 void AXObject::ElementsFromAttribute(HeapVector<Member<Element>>& elements,
954 const QualifiedName& attribute) const {
955 Vector<String> ids;
956 TokenVectorFromAttribute(ids, attribute);
957 if (ids.IsEmpty())
958 return;
959
960 TreeScope& scope = GetNode()->GetTreeScope();
961 for (const auto& id : ids) {
962 if (Element* id_element = scope.getElementById(AtomicString(id)))
963 elements.push_back(id_element);
964 }
965 }
966
967 void AXObject::AriaLabelledbyElementVector(
968 HeapVector<Member<Element>>& elements) const {
969 // Try both spellings, but prefer aria-labelledby, which is the official spec.
970 ElementsFromAttribute(elements, aria_labelledbyAttr);
971 if (!elements.size())
972 ElementsFromAttribute(elements, aria_labeledbyAttr);
973 }
974
975 String AXObject::TextFromAriaLabelledby(
976 AXObjectSet& visited,
977 AXRelatedObjectVector* related_objects) const {
978 HeapVector<Member<Element>> elements;
979 AriaLabelledbyElementVector(elements);
980 return TextFromElements(true, visited, elements, related_objects);
981 }
982
983 String AXObject::TextFromAriaDescribedby(
984 AXRelatedObjectVector* related_objects) const {
985 AXObjectSet visited;
986 HeapVector<Member<Element>> elements;
987 ElementsFromAttribute(elements, aria_describedbyAttr);
988 return TextFromElements(true, visited, elements, related_objects);
989 }
990
991 RGBA32 AXObject::BackgroundColor() const {
992 UpdateCachedAttributeValuesIfNeeded();
993 return cached_background_color_;
994 }
995
996 AccessibilityOrientation AXObject::Orientation() const {
997 // In ARIA 1.1, the default value for aria-orientation changed from
998 // horizontal to undefined.
999 return kAccessibilityOrientationUndefined;
1000 }
1001
1002 AXSupportedAction AXObject::Action() const {
1003 if (!ActionElement())
1004 return AXSupportedAction::kNone;
1005
1006 switch (RoleValue()) {
1007 case kButtonRole:
1008 case kToggleButtonRole:
1009 return AXSupportedAction::kPress;
1010 case kTextFieldRole:
1011 return AXSupportedAction::kActivate;
1012 case kRadioButtonRole:
1013 return AXSupportedAction::kSelect;
1014 case kCheckBoxRole:
1015 case kSwitchRole:
1016 return CheckedState() == kButtonStateOff ? AXSupportedAction::kCheck
1017 : AXSupportedAction::kUncheck;
1018 case kLinkRole:
1019 return AXSupportedAction::kJump;
1020 case kPopUpButtonRole:
1021 return AXSupportedAction::kOpen;
1022 default:
1023 return AXSupportedAction::kClick;
1024 }
1025 }
1026
1027 bool AXObject::IsMultiline() const {
1028 Node* node = this->GetNode();
1029 if (!node)
1030 return false;
1031
1032 if (isHTMLTextAreaElement(*node))
1033 return true;
1034
1035 if (HasEditableStyle(*node))
1036 return true;
1037
1038 if (!IsNativeTextControl() && !IsNonNativeTextControl())
1039 return false;
1040
1041 return EqualIgnoringASCIICase(GetAttribute(aria_multilineAttr), "true");
1042 }
1043
1044 bool AXObject::AriaPressedIsPresent() const {
1045 return !GetAttribute(aria_pressedAttr).IsEmpty();
1046 }
1047
1048 bool AXObject::SupportsActiveDescendant() const {
1049 // According to the ARIA Spec, all ARIA composite widgets, ARIA text boxes
1050 // and ARIA groups should be able to expose an active descendant.
1051 // Implicitly, <input> and <textarea> elements should also have this ability.
1052 switch (AriaRoleAttribute()) {
1053 case kComboBoxRole:
1054 case kGridRole:
1055 case kGroupRole:
1056 case kListBoxRole:
1057 case kMenuRole:
1058 case kMenuBarRole:
1059 case kRadioGroupRole:
1060 case kRowRole:
1061 case kSearchBoxRole:
1062 case kTabListRole:
1063 case kTextFieldRole:
1064 case kToolbarRole:
1065 case kTreeRole:
1066 case kTreeGridRole:
1067 return true;
1068 default:
1069 return false;
1070 }
1071 }
1072
1073 bool AXObject::SupportsARIAAttributes() const {
1074 return IsLiveRegion() || SupportsARIADragging() || SupportsARIADropping() ||
1075 SupportsARIAFlowTo() || SupportsARIAOwns() ||
1076 HasAttribute(aria_labelAttr);
1077 }
1078
1079 bool AXObject::SupportsRangeValue() const {
1080 return IsProgressIndicator() || IsMeter() || IsSlider() || IsScrollbar() ||
1081 IsSpinButton();
1082 }
1083
1084 bool AXObject::SupportsSetSizeAndPosInSet() const {
1085 AXObject* parent = ParentObject();
1086 if (!parent)
1087 return false;
1088
1089 int role = RoleValue();
1090 int parent_role = parent->RoleValue();
1091
1092 if ((role == kListBoxOptionRole && parent_role == kListBoxRole) ||
1093 (role == kListItemRole && parent_role == kListRole) ||
1094 (role == kMenuItemRole && parent_role == kMenuRole) ||
1095 (role == kRadioButtonRole) ||
1096 (role == kTabRole && parent_role == kTabListRole) ||
1097 (role == kTreeItemRole && parent_role == kTreeRole) ||
1098 (role == kTreeItemRole && parent_role == kGroupRole)) {
1099 return true;
1100 }
1101
1102 return false;
1103 }
1104
1105 int AXObject::IndexInParent() const {
1106 if (!ParentObject())
1107 return 0;
1108
1109 const auto& siblings = ParentObject()->Children();
1110 int child_count = siblings.size();
1111
1112 for (int index = 0; index < child_count; ++index) {
1113 if (siblings[index].Get() == this) {
1114 return index;
1115 }
1116 }
1117 return 0;
1118 }
1119
1120 bool AXObject::IsLiveRegion() const {
1121 const AtomicString& live_region = LiveRegionStatus();
1122 return EqualIgnoringASCIICase(live_region, "polite") ||
1123 EqualIgnoringASCIICase(live_region, "assertive");
1124 }
1125
1126 AXObject* AXObject::LiveRegionRoot() const {
1127 UpdateCachedAttributeValuesIfNeeded();
1128 return cached_live_region_root_;
1129 }
1130
1131 const AtomicString& AXObject::ContainerLiveRegionStatus() const {
1132 UpdateCachedAttributeValuesIfNeeded();
1133 return cached_live_region_root_ ? cached_live_region_root_->LiveRegionStatus()
1134 : g_null_atom;
1135 }
1136
1137 const AtomicString& AXObject::ContainerLiveRegionRelevant() const {
1138 UpdateCachedAttributeValuesIfNeeded();
1139 return cached_live_region_root_
1140 ? cached_live_region_root_->LiveRegionRelevant()
1141 : g_null_atom;
1142 }
1143
1144 bool AXObject::ContainerLiveRegionAtomic() const {
1145 UpdateCachedAttributeValuesIfNeeded();
1146 return cached_live_region_root_ &&
1147 cached_live_region_root_->LiveRegionAtomic();
1148 }
1149
1150 bool AXObject::ContainerLiveRegionBusy() const {
1151 UpdateCachedAttributeValuesIfNeeded();
1152 return cached_live_region_root_ && cached_live_region_root_->LiveRegionBusy();
1153 }
1154
1155 AXObject* AXObject::ElementAccessibilityHitTest(const IntPoint& point) const {
1156 // Check if there are any mock elements that need to be handled.
1157 for (const auto& child : children_) {
1158 if (child->IsMockObject() &&
1159 child->GetBoundsInFrameCoordinates().Contains(point))
1160 return child->ElementAccessibilityHitTest(point);
1161 }
1162
1163 return const_cast<AXObject*>(this);
1164 }
1165
1166 const AXObject::AXObjectVector& AXObject::Children() {
1167 UpdateChildrenIfNecessary();
1168
1169 return children_;
1170 }
1171
1172 AXObject* AXObject::ParentObject() const {
1173 if (IsDetached())
1174 return 0;
1175
1176 if (parent_)
1177 return parent_;
1178
1179 if (AxObjectCache().IsAriaOwned(this))
1180 return AxObjectCache().GetAriaOwnedParent(this);
1181
1182 return ComputeParent();
1183 }
1184
1185 AXObject* AXObject::ParentObjectIfExists() const {
1186 if (IsDetached())
1187 return 0;
1188
1189 if (parent_)
1190 return parent_;
1191
1192 return ComputeParentIfExists();
1193 }
1194
1195 AXObject* AXObject::ParentObjectUnignored() const {
1196 AXObject* parent;
1197 for (parent = ParentObject(); parent && parent->AccessibilityIsIgnored();
1198 parent = parent->ParentObject()) {
1199 }
1200
1201 return parent;
1202 }
1203
1204 void AXObject::UpdateChildrenIfNecessary() {
1205 if (!HasChildren())
1206 AddChildren();
1207 }
1208
1209 void AXObject::ClearChildren() {
1210 // Detach all weak pointers from objects to their parents.
1211 for (const auto& child : children_)
1212 child->DetachFromParent();
1213
1214 children_.clear();
1215 have_children_ = false;
1216 }
1217
1218 Document* AXObject::GetDocument() const {
1219 FrameView* frame_view = DocumentFrameView();
1220 if (!frame_view)
1221 return 0;
1222
1223 return frame_view->GetFrame().GetDocument();
1224 }
1225
1226 FrameView* AXObject::DocumentFrameView() const {
1227 const AXObject* object = this;
1228 while (object && !object->IsAXLayoutObject())
1229 object = object->ParentObject();
1230
1231 if (!object)
1232 return 0;
1233
1234 return object->DocumentFrameView();
1235 }
1236
1237 String AXObject::Language() const {
1238 const AtomicString& lang = GetAttribute(langAttr);
1239 if (!lang.IsEmpty())
1240 return lang;
1241
1242 AXObject* parent = ParentObject();
1243
1244 // As a last resort, fall back to the content language specified in the meta
1245 // tag.
1246 if (!parent) {
1247 Document* doc = GetDocument();
1248 if (doc)
1249 return doc->ContentLanguage();
1250 return g_null_atom;
1251 }
1252
1253 return parent->Language();
1254 }
1255
1256 bool AXObject::HasAttribute(const QualifiedName& attribute) const {
1257 Node* element_node = GetNode();
1258 if (!element_node)
1259 return false;
1260
1261 if (!element_node->IsElementNode())
1262 return false;
1263
1264 Element* element = ToElement(element_node);
1265 return element->FastHasAttribute(attribute);
1266 }
1267
1268 const AtomicString& AXObject::GetAttribute(
1269 const QualifiedName& attribute) const {
1270 Node* element_node = GetNode();
1271 if (!element_node)
1272 return g_null_atom;
1273
1274 if (!element_node->IsElementNode())
1275 return g_null_atom;
1276
1277 Element* element = ToElement(element_node);
1278 return element->FastGetAttribute(attribute);
1279 }
1280
1281 //
1282 // Scrollable containers.
1283 //
1284
1285 bool AXObject::IsScrollableContainer() const {
1286 return !!GetScrollableAreaIfScrollable();
1287 }
1288
1289 IntPoint AXObject::GetScrollOffset() const {
1290 ScrollableArea* area = GetScrollableAreaIfScrollable();
1291 if (!area)
1292 return IntPoint();
1293
1294 return IntPoint(area->ScrollOffsetInt().Width(),
1295 area->ScrollOffsetInt().Height());
1296 }
1297
1298 IntPoint AXObject::MinimumScrollOffset() const {
1299 ScrollableArea* area = GetScrollableAreaIfScrollable();
1300 if (!area)
1301 return IntPoint();
1302
1303 return IntPoint(area->MinimumScrollOffsetInt().Width(),
1304 area->MinimumScrollOffsetInt().Height());
1305 }
1306
1307 IntPoint AXObject::MaximumScrollOffset() const {
1308 ScrollableArea* area = GetScrollableAreaIfScrollable();
1309 if (!area)
1310 return IntPoint();
1311
1312 return IntPoint(area->MaximumScrollOffsetInt().Width(),
1313 area->MaximumScrollOffsetInt().Height());
1314 }
1315
1316 void AXObject::SetScrollOffset(const IntPoint& offset) const {
1317 ScrollableArea* area = GetScrollableAreaIfScrollable();
1318 if (!area)
1319 return;
1320
1321 // TODO(bokan): This should potentially be a UserScroll.
1322 area->SetScrollOffset(ScrollOffset(offset.X(), offset.Y()),
1323 kProgrammaticScroll);
1324 }
1325
1326 void AXObject::GetRelativeBounds(AXObject** out_container,
1327 FloatRect& out_bounds_in_container,
1328 SkMatrix44& out_container_transform) const {
1329 *out_container = nullptr;
1330 out_bounds_in_container = FloatRect();
1331 out_container_transform.setIdentity();
1332
1333 // First check if it has explicit bounds, for example if this element is tied
1334 // to a canvas path. When explicit coordinates are provided, the ID of the
1335 // explicit container element that the coordinates are relative to must be
1336 // provided too.
1337 if (!explicit_element_rect_.IsEmpty()) {
1338 *out_container = AxObjectCache().ObjectFromAXID(explicit_container_id_);
1339 if (*out_container) {
1340 out_bounds_in_container = FloatRect(explicit_element_rect_);
1341 return;
1342 }
1343 }
1344
1345 LayoutObject* layout_object = LayoutObjectForRelativeBounds();
1346 if (!layout_object)
1347 return;
1348
1349 if (IsWebArea()) {
1350 if (layout_object->GetFrame()->View())
1351 out_bounds_in_container.SetSize(
1352 FloatSize(layout_object->GetFrame()->View()->ContentsSize()));
1353 return;
1354 }
1355
1356 // First compute the container. The container must be an ancestor in the
1357 // accessibility tree, and its LayoutObject must be an ancestor in the layout
1358 // tree. Get the first such ancestor that's either scrollable or has a paint
1359 // layer.
1360 AXObject* container = ParentObjectUnignored();
1361 LayoutObject* container_layout_object = nullptr;
1362 while (container) {
1363 container_layout_object = container->GetLayoutObject();
1364 if (container_layout_object &&
1365 container_layout_object->IsBoxModelObject() &&
1366 layout_object->IsDescendantOf(container_layout_object)) {
1367 if (container->IsScrollableContainer() ||
1368 container_layout_object->HasLayer())
1369 break;
1370 }
1371
1372 container = container->ParentObjectUnignored();
1373 }
1374
1375 if (!container)
1376 return;
1377 *out_container = container;
1378 out_bounds_in_container =
1379 layout_object->LocalBoundingBoxRectForAccessibility();
1380
1381 // If the container has a scroll offset, subtract that out because we want our
1382 // bounds to be relative to the *unscrolled* position of the container object.
1383 ScrollableArea* scrollable_area = container->GetScrollableAreaIfScrollable();
1384 if (scrollable_area && !container->IsWebArea()) {
1385 ScrollOffset scroll_offset = scrollable_area->GetScrollOffset();
1386 out_bounds_in_container.Move(scroll_offset);
1387 }
1388
1389 // Compute the transform between the container's coordinate space and this
1390 // object. If the transform is just a simple translation, apply that to the
1391 // bounding box, but if it's a non-trivial transformation like a rotation,
1392 // scaling, etc. then return the full matrix instead.
1393 TransformationMatrix transform = layout_object->LocalToAncestorTransform(
1394 ToLayoutBoxModelObject(container_layout_object));
1395 if (transform.IsIdentityOr2DTranslation()) {
1396 out_bounds_in_container.Move(transform.To2DTranslation());
1397 } else {
1398 out_container_transform = TransformationMatrix::ToSkMatrix44(transform);
1399 }
1400 }
1401
1402 LayoutRect AXObject::GetBoundsInFrameCoordinates() const {
1403 AXObject* container = nullptr;
1404 FloatRect bounds;
1405 SkMatrix44 transform;
1406 GetRelativeBounds(&container, bounds, transform);
1407 FloatRect computed_bounds(0, 0, bounds.Width(), bounds.Height());
1408 while (container && container != this) {
1409 computed_bounds.Move(bounds.X(), bounds.Y());
1410 if (!container->IsWebArea()) {
1411 computed_bounds.Move(-container->GetScrollOffset().X(),
1412 -container->GetScrollOffset().Y());
1413 }
1414 if (!transform.isIdentity()) {
1415 TransformationMatrix transformation_matrix(transform);
1416 transformation_matrix.MapRect(computed_bounds);
1417 }
1418 container->GetRelativeBounds(&container, bounds, transform);
1419 }
1420 return LayoutRect(computed_bounds);
1421 }
1422
1423 //
1424 // Modify or take an action on an object.
1425 //
1426
1427 bool AXObject::Press() {
1428 Document* document = GetDocument();
1429 if (!document)
1430 return false;
1431
1432 UserGestureIndicator gesture_indicator(DocumentUserGestureToken::Create(
1433 document, UserGestureToken::kNewGesture));
1434 Element* action_elem = ActionElement();
1435 if (action_elem) {
1436 action_elem->AccessKeyAction(true);
1437 return true;
1438 }
1439
1440 if (CanSetFocusAttribute()) {
1441 SetFocused(true);
1442 return true;
1443 }
1444
1445 return false;
1446 }
1447
1448 void AXObject::ScrollToMakeVisible() const {
1449 IntRect object_rect = PixelSnappedIntRect(GetBoundsInFrameCoordinates());
1450 object_rect.SetLocation(IntPoint());
1451 ScrollToMakeVisibleWithSubFocus(object_rect);
1452 }
1453
1454 // This is a 1-dimensional scroll offset helper function that's applied
1455 // separately in the horizontal and vertical directions, because the
1456 // logic is the same. The goal is to compute the best scroll offset
1457 // in order to make an object visible within a viewport.
1458 //
1459 // If the object is already fully visible, returns the same scroll
1460 // offset.
1461 //
1462 // In case the whole object cannot fit, you can specify a
1463 // subfocus - a smaller region within the object that should
1464 // be prioritized. If the whole object can fit, the subfocus is
1465 // ignored.
1466 //
1467 // If possible, the object and subfocus are centered within the
1468 // viewport.
1469 //
1470 // Example 1: the object is already visible, so nothing happens.
1471 // +----------Viewport---------+
1472 // +---Object---+
1473 // +--SubFocus--+
1474 //
1475 // Example 2: the object is not fully visible, so it's centered
1476 // within the viewport.
1477 // Before:
1478 // +----------Viewport---------+
1479 // +---Object---+
1480 // +--SubFocus--+
1481 //
1482 // After:
1483 // +----------Viewport---------+
1484 // +---Object---+
1485 // +--SubFocus--+
1486 //
1487 // Example 3: the object is larger than the viewport, so the
1488 // viewport moves to show as much of the object as possible,
1489 // while also trying to center the subfocus.
1490 // Before:
1491 // +----------Viewport---------+
1492 // +---------------Object--------------+
1493 // +-SubFocus-+
1494 //
1495 // After:
1496 // +----------Viewport---------+
1497 // +---------------Object--------------+
1498 // +-SubFocus-+
1499 //
1500 // When constraints cannot be fully satisfied, the min
1501 // (left/top) position takes precedence over the max (right/bottom).
1502 //
1503 // Note that the return value represents the ideal new scroll offset.
1504 // This may be out of range - the calling function should clip this
1505 // to the available range.
1506 static int ComputeBestScrollOffset(int current_scroll_offset,
1507 int subfocus_min,
1508 int subfocus_max,
1509 int object_min,
1510 int object_max,
1511 int viewport_min,
1512 int viewport_max) {
1513 int viewport_size = viewport_max - viewport_min;
1514
1515 // If the object size is larger than the viewport size, consider
1516 // only a portion that's as large as the viewport, centering on
1517 // the subfocus as much as possible.
1518 if (object_max - object_min > viewport_size) {
1519 // Since it's impossible to fit the whole object in the
1520 // viewport, exit now if the subfocus is already within the viewport.
1521 if (subfocus_min - current_scroll_offset >= viewport_min &&
1522 subfocus_max - current_scroll_offset <= viewport_max)
1523 return current_scroll_offset;
1524
1525 // Subfocus must be within focus.
1526 subfocus_min = std::max(subfocus_min, object_min);
1527 subfocus_max = std::min(subfocus_max, object_max);
1528
1529 // Subfocus must be no larger than the viewport size; favor top/left.
1530 if (subfocus_max - subfocus_min > viewport_size)
1531 subfocus_max = subfocus_min + viewport_size;
1532
1533 // Compute the size of an object centered on the subfocus, the size of the
1534 // viewport.
1535 int centered_object_min = (subfocus_min + subfocus_max - viewport_size) / 2;
1536 int centered_object_max = centered_object_min + viewport_size;
1537
1538 object_min = std::max(object_min, centered_object_min);
1539 object_max = std::min(object_max, centered_object_max);
1540 }
1541
1542 // Exit now if the focus is already within the viewport.
1543 if (object_min - current_scroll_offset >= viewport_min &&
1544 object_max - current_scroll_offset <= viewport_max)
1545 return current_scroll_offset;
1546
1547 // Center the object in the viewport.
1548 return (object_min + object_max - viewport_min - viewport_max) / 2;
1549 }
1550
1551 void AXObject::ScrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const {
1552 // Search up the parent chain until we find the first one that's scrollable.
1553 const AXObject* scroll_parent = ParentObject() ? ParentObject() : this;
1554 ScrollableArea* scrollable_area = 0;
1555 while (scroll_parent) {
1556 scrollable_area = scroll_parent->GetScrollableAreaIfScrollable();
1557 if (scrollable_area)
1558 break;
1559 scroll_parent = scroll_parent->ParentObject();
1560 }
1561 if (!scroll_parent || !scrollable_area)
1562 return;
1563
1564 IntRect object_rect = PixelSnappedIntRect(GetBoundsInFrameCoordinates());
1565 IntSize scroll_offset = scrollable_area->ScrollOffsetInt();
1566 IntRect scroll_visible_rect = scrollable_area->VisibleContentRect();
1567
1568 // Convert the object rect into local coordinates.
1569 if (!scroll_parent->IsWebArea()) {
1570 object_rect.MoveBy(IntPoint(scroll_offset));
1571 object_rect.MoveBy(
1572 -PixelSnappedIntRect(scroll_parent->GetBoundsInFrameCoordinates())
1573 .Location());
1574 }
1575
1576 int desired_x = ComputeBestScrollOffset(
1577 scroll_offset.Width(), object_rect.X() + subfocus.X(),
1578 object_rect.X() + subfocus.MaxX(), object_rect.X(), object_rect.MaxX(), 0,
1579 scroll_visible_rect.Width());
1580 int desired_y = ComputeBestScrollOffset(
1581 scroll_offset.Height(), object_rect.Y() + subfocus.Y(),
1582 object_rect.Y() + subfocus.MaxY(), object_rect.Y(), object_rect.MaxY(), 0,
1583 scroll_visible_rect.Height());
1584
1585 scroll_parent->SetScrollOffset(IntPoint(desired_x, desired_y));
1586
1587 // Convert the subfocus into the coordinates of the scroll parent.
1588 IntRect new_subfocus = subfocus;
1589 IntRect new_element_rect = PixelSnappedIntRect(GetBoundsInFrameCoordinates());
1590 IntRect scroll_parent_rect =
1591 PixelSnappedIntRect(scroll_parent->GetBoundsInFrameCoordinates());
1592 new_subfocus.Move(new_element_rect.X(), new_element_rect.Y());
1593 new_subfocus.Move(-scroll_parent_rect.X(), -scroll_parent_rect.Y());
1594
1595 if (scroll_parent->ParentObject()) {
1596 // Recursively make sure the scroll parent itself is visible.
1597 scroll_parent->ScrollToMakeVisibleWithSubFocus(new_subfocus);
1598 } else {
1599 // To minimize the number of notifications, only fire one on the topmost
1600 // object that has been scrolled.
1601 AxObjectCache().PostNotification(const_cast<AXObject*>(this),
1602 AXObjectCacheImpl::kAXLocationChanged);
1603 }
1604 }
1605
1606 void AXObject::ScrollToGlobalPoint(const IntPoint& global_point) const {
1607 // Search up the parent chain and create a vector of all scrollable parent
1608 // objects and ending with this object itself.
1609 HeapVector<Member<const AXObject>> objects;
1610 AXObject* parent_object;
1611 for (parent_object = this->ParentObject(); parent_object;
1612 parent_object = parent_object->ParentObject()) {
1613 if (parent_object->GetScrollableAreaIfScrollable())
1614 objects.push_front(parent_object);
1615 }
1616 objects.push_back(this);
1617
1618 // Start with the outermost scrollable (the main window) and try to scroll the
1619 // next innermost object to the given point.
1620 int offset_x = 0, offset_y = 0;
1621 IntPoint point = global_point;
1622 size_t levels = objects.size() - 1;
1623 for (size_t i = 0; i < levels; i++) {
1624 const AXObject* outer = objects[i];
1625 const AXObject* inner = objects[i + 1];
1626 ScrollableArea* scrollable_area = outer->GetScrollableAreaIfScrollable();
1627
1628 IntRect inner_rect =
1629 inner->IsWebArea()
1630 ? PixelSnappedIntRect(
1631 inner->ParentObject()->GetBoundsInFrameCoordinates())
1632 : PixelSnappedIntRect(inner->GetBoundsInFrameCoordinates());
1633 IntRect object_rect = inner_rect;
1634 IntSize scroll_offset = scrollable_area->ScrollOffsetInt();
1635
1636 // Convert the object rect into local coordinates.
1637 object_rect.Move(offset_x, offset_y);
1638 if (!outer->IsWebArea())
1639 object_rect.Move(scroll_offset.Width(), scroll_offset.Height());
1640
1641 int desired_x = ComputeBestScrollOffset(
1642 0, object_rect.X(), object_rect.MaxX(), object_rect.X(),
1643 object_rect.MaxX(), point.X(), point.X());
1644 int desired_y = ComputeBestScrollOffset(
1645 0, object_rect.Y(), object_rect.MaxY(), object_rect.Y(),
1646 object_rect.MaxY(), point.Y(), point.Y());
1647 outer->SetScrollOffset(IntPoint(desired_x, desired_y));
1648
1649 if (outer->IsWebArea() && !inner->IsWebArea()) {
1650 // If outer object we just scrolled is a web area (frame) but the inner
1651 // object is not, keep track of the coordinate transformation to apply to
1652 // future nested calculations.
1653 scroll_offset = scrollable_area->ScrollOffsetInt();
1654 offset_x -= (scroll_offset.Width() + point.X());
1655 offset_y -= (scroll_offset.Height() + point.Y());
1656 point.Move(scroll_offset.Width() - inner_rect.Width(),
1657 scroll_offset.Height() - inner_rect.Y());
1658 } else if (inner->IsWebArea()) {
1659 // Otherwise, if the inner object is a web area, reset the coordinate
1660 // transformation.
1661 offset_x = 0;
1662 offset_y = 0;
1663 }
1664 }
1665
1666 // To minimize the number of notifications, only fire one on the topmost
1667 // object that has been scrolled.
1668 DCHECK(objects[0]);
1669 // TODO(nektar): Switch to postNotification(objects[0] and remove |getNode|.
1670 AxObjectCache().PostNotification(objects[0]->GetNode(),
1671 AXObjectCacheImpl::kAXLocationChanged);
1672 }
1673
1674 void AXObject::SetSequentialFocusNavigationStartingPoint() {
1675 // Call it on the nearest ancestor that overrides this with a specific
1676 // implementation.
1677 if (ParentObject())
1678 ParentObject()->SetSequentialFocusNavigationStartingPoint();
1679 }
1680
1681 void AXObject::NotifyIfIgnoredValueChanged() {
1682 bool is_ignored = AccessibilityIsIgnored();
1683 if (LastKnownIsIgnoredValue() != is_ignored) {
1684 AxObjectCache().ChildrenChanged(ParentObject());
1685 SetLastKnownIsIgnoredValue(is_ignored);
1686 }
1687 }
1688
1689 void AXObject::SelectionChanged() {
1690 if (AXObject* parent = ParentObjectIfExists())
1691 parent->SelectionChanged();
1692 }
1693
1694 int AXObject::LineForPosition(const VisiblePosition& position) const {
1695 if (position.IsNull() || !GetNode())
1696 return -1;
1697
1698 // If the position is not in the same editable region as this AX object,
1699 // return -1.
1700 Node* container_node = position.DeepEquivalent().ComputeContainerNode();
1701 if (!container_node->IsShadowIncludingInclusiveAncestorOf(GetNode()) &&
1702 !GetNode()->IsShadowIncludingInclusiveAncestorOf(container_node))
1703 return -1;
1704
1705 int line_count = -1;
1706 VisiblePosition current_position = position;
1707 VisiblePosition previous_position;
1708
1709 // Move up until we get to the top.
1710 // FIXME: This only takes us to the top of the rootEditableElement, not the
1711 // top of the top document.
1712 do {
1713 previous_position = current_position;
1714 current_position = PreviousLinePosition(current_position, LayoutUnit(),
1715 kHasEditableAXRole);
1716 ++line_count;
1717 } while (current_position.IsNotNull() &&
1718 !InSameLine(current_position, previous_position));
1719
1720 return line_count;
1721 }
1722
1723 bool AXObject::IsARIAControl(AccessibilityRole aria_role) {
1724 return IsARIAInput(aria_role) || aria_role == kButtonRole ||
1725 aria_role == kComboBoxRole || aria_role == kSliderRole;
1726 }
1727
1728 bool AXObject::IsARIAInput(AccessibilityRole aria_role) {
1729 return aria_role == kRadioButtonRole || aria_role == kCheckBoxRole ||
1730 aria_role == kTextFieldRole || aria_role == kSwitchRole ||
1731 aria_role == kSearchBoxRole;
1732 }
1733
1734 AccessibilityRole AXObject::AriaRoleToWebCoreRole(const String& value) {
1735 DCHECK(!value.IsEmpty());
1736
1737 static const ARIARoleMap* role_map = CreateARIARoleMap();
1738
1739 Vector<String> role_vector;
1740 value.Split(' ', role_vector);
1741 AccessibilityRole role = kUnknownRole;
1742 for (const auto& child : role_vector) {
1743 role = role_map->at(child);
1744 if (role)
1745 return role;
1746 }
1747
1748 return role;
1749 }
1750
1751 bool AXObject::IsInsideFocusableElementOrARIAWidget(const Node& node) {
1752 const Node* cur_node = &node;
1753 do {
1754 if (cur_node->IsElementNode()) {
1755 const Element* element = ToElement(cur_node);
1756 if (element->IsFocusable())
1757 return true;
1758 String role = element->getAttribute("role");
1759 if (!role.IsEmpty() && AXObject::IncludesARIAWidgetRole(role))
1760 return true;
1761 if (HasInteractiveARIAAttribute(*element))
1762 return true;
1763 }
1764 cur_node = cur_node->parentNode();
1765 } while (cur_node && !isHTMLBodyElement(node));
1766 return false;
1767 }
1768
1769 bool AXObject::HasInteractiveARIAAttribute(const Element& element) {
1770 for (size_t i = 0; i < WTF_ARRAY_LENGTH(g_aria_interactive_widget_attributes);
1771 ++i) {
1772 const char* attribute = g_aria_interactive_widget_attributes[i];
1773 if (element.hasAttribute(attribute)) {
1774 return true;
1775 }
1776 }
1777 return false;
1778 }
1779
1780 bool AXObject::IncludesARIAWidgetRole(const String& role) {
1781 static const HashSet<String, CaseFoldingHash>* role_set =
1782 CreateARIARoleWidgetSet();
1783
1784 Vector<String> role_vector;
1785 role.Split(' ', role_vector);
1786 for (const auto& child : role_vector) {
1787 if (role_set->Contains(child))
1788 return true;
1789 }
1790 return false;
1791 }
1792
1793 bool AXObject::NameFromContents() const {
1794 // ARIA 1.1, section 5.2.7.5.
1795 switch (RoleValue()) {
1796 case kAnchorRole:
1797 case kButtonRole:
1798 case kCellRole:
1799 case kCheckBoxRole:
1800 case kColumnHeaderRole:
1801 case kDirectoryRole:
1802 case kDisclosureTriangleRole:
1803 case kHeadingRole:
1804 case kLineBreakRole:
1805 case kLinkRole:
1806 case kListBoxOptionRole:
1807 case kListItemRole:
1808 case kMenuItemRole:
1809 case kMenuItemCheckBoxRole:
1810 case kMenuItemRadioRole:
1811 case kMenuListOptionRole:
1812 case kPopUpButtonRole:
1813 case kRadioButtonRole:
1814 case kRowHeaderRole:
1815 case kStaticTextRole:
1816 case kStatusRole:
1817 case kSwitchRole:
1818 case kTabRole:
1819 case kToggleButtonRole:
1820 case kTreeItemRole:
1821 case kUserInterfaceTooltipRole:
1822 return true;
1823 case kRowRole: {
1824 // Spec says we should always expose the name on rows,
1825 // but for performance reasons we only do it
1826 // if the row might receive focus
1827 if (AncestorExposesActiveDescendant()) {
1828 return true;
1829 }
1830 const Node* node = this->GetNode();
1831 return node && node->IsElementNode() && ToElement(node)->IsFocusable();
1832 }
1833 default:
1834 return false;
1835 }
1836 }
1837
1838 AccessibilityRole AXObject::ButtonRoleType() const {
1839 // If aria-pressed is present, then it should be exposed as a toggle button.
1840 // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed
1841 if (AriaPressedIsPresent())
1842 return kToggleButtonRole;
1843 if (AriaHasPopup())
1844 return kPopUpButtonRole;
1845 // We don't contemplate RadioButtonRole, as it depends on the input
1846 // type.
1847
1848 return kButtonRole;
1849 }
1850
1851 const AtomicString& AXObject::RoleName(AccessibilityRole role) {
1852 static const Vector<AtomicString>* role_name_vector = CreateRoleNameVector();
1853
1854 return role_name_vector->at(role);
1855 }
1856
1857 const AtomicString& AXObject::InternalRoleName(AccessibilityRole role) {
1858 static const Vector<AtomicString>* internal_role_name_vector =
1859 CreateInternalRoleNameVector();
1860
1861 return internal_role_name_vector->at(role);
1862 }
1863
1864 DEFINE_TRACE(AXObject) {
1865 visitor->Trace(children_);
1866 visitor->Trace(parent_);
1867 visitor->Trace(cached_live_region_root_);
1868 visitor->Trace(ax_object_cache_);
1869 }
1870
1871 } // namespace blink
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698