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