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