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 |