OLD | NEW |
| (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 | |
OLD | NEW |