| Index: third_party/WebKit/Source/modules/accessibility/AXObject.cpp
|
| diff --git a/third_party/WebKit/Source/modules/accessibility/AXObject.cpp b/third_party/WebKit/Source/modules/accessibility/AXObject.cpp
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2cae448cea5924e6a4d2b86e6dc2643618255bf7
|
| --- /dev/null
|
| +++ b/third_party/WebKit/Source/modules/accessibility/AXObject.cpp
|
| @@ -0,0 +1,1871 @@
|
| +/*
|
| + * Copyright (C) 2008, 2009, 2011 Apple Inc. All rights reserved.
|
| + *
|
| + * Redistribution and use in source and binary forms, with or without
|
| + * modification, are permitted provided that the following conditions
|
| + * are met:
|
| + *
|
| + * 1. Redistributions of source code must retain the above copyright
|
| + * notice, this list of conditions and the following disclaimer.
|
| + * 2. Redistributions in binary form must reproduce the above copyright
|
| + * notice, this list of conditions and the following disclaimer in the
|
| + * documentation and/or other materials provided with the distribution.
|
| + * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
|
| + * its contributors may be used to endorse or promote products derived
|
| + * from this software without specific prior written permission.
|
| + *
|
| + * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
|
| + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
| + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
| + * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
|
| + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
| + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
| + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
| + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
| + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
|
| + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
| + */
|
| +
|
| +#include "modules/accessibility/AXObject.h"
|
| +
|
| +#include "SkMatrix44.h"
|
| +#include "core/InputTypeNames.h"
|
| +#include "core/css/resolver/StyleResolver.h"
|
| +#include "core/dom/AccessibleNode.h"
|
| +#include "core/dom/DocumentUserGestureToken.h"
|
| +#include "core/editing/EditingUtilities.h"
|
| +#include "core/editing/VisibleUnits.h"
|
| +#include "core/frame/FrameView.h"
|
| +#include "core/frame/LocalFrame.h"
|
| +#include "core/frame/Settings.h"
|
| +#include "core/html/HTMLDialogElement.h"
|
| +#include "core/html/HTMLFrameOwnerElement.h"
|
| +#include "core/html/HTMLInputElement.h"
|
| +#include "core/html/parser/HTMLParserIdioms.h"
|
| +#include "core/layout/LayoutBoxModelObject.h"
|
| +#include "modules/accessibility/AXObjectCacheImpl.h"
|
| +#include "platform/UserGestureIndicator.h"
|
| +#include "platform/text/PlatformLocale.h"
|
| +#include "platform/wtf/HashSet.h"
|
| +#include "platform/wtf/StdLibExtras.h"
|
| +#include "platform/wtf/text/WTFString.h"
|
| +
|
| +using blink::WebLocalizedString;
|
| +
|
| +namespace blink {
|
| +
|
| +using namespace HTMLNames;
|
| +
|
| +namespace {
|
| +typedef HashMap<String, AccessibilityRole, CaseFoldingHash> ARIARoleMap;
|
| +typedef HashSet<String, CaseFoldingHash> ARIAWidgetSet;
|
| +
|
| +struct RoleEntry {
|
| + const char* aria_role;
|
| + AccessibilityRole webcore_role;
|
| +};
|
| +
|
| +const RoleEntry kRoles[] = {{"alert", kAlertRole},
|
| + {"alertdialog", kAlertDialogRole},
|
| + {"application", kApplicationRole},
|
| + {"article", kArticleRole},
|
| + {"banner", kBannerRole},
|
| + {"button", kButtonRole},
|
| + {"cell", kCellRole},
|
| + {"checkbox", kCheckBoxRole},
|
| + {"columnheader", kColumnHeaderRole},
|
| + {"combobox", kComboBoxRole},
|
| + {"complementary", kComplementaryRole},
|
| + {"contentinfo", kContentInfoRole},
|
| + {"definition", kDefinitionRole},
|
| + {"dialog", kDialogRole},
|
| + {"directory", kDirectoryRole},
|
| + {"document", kDocumentRole},
|
| + {"feed", kFeedRole},
|
| + {"figure", kFigureRole},
|
| + {"form", kFormRole},
|
| + {"grid", kGridRole},
|
| + {"gridcell", kCellRole},
|
| + {"group", kGroupRole},
|
| + {"heading", kHeadingRole},
|
| + {"img", kImageRole},
|
| + {"link", kLinkRole},
|
| + {"list", kListRole},
|
| + {"listbox", kListBoxRole},
|
| + {"listitem", kListItemRole},
|
| + {"log", kLogRole},
|
| + {"main", kMainRole},
|
| + {"marquee", kMarqueeRole},
|
| + {"math", kMathRole},
|
| + {"menu", kMenuRole},
|
| + {"menubar", kMenuBarRole},
|
| + {"menuitem", kMenuItemRole},
|
| + {"menuitemcheckbox", kMenuItemCheckBoxRole},
|
| + {"menuitemradio", kMenuItemRadioRole},
|
| + {"navigation", kNavigationRole},
|
| + {"none", kNoneRole},
|
| + {"note", kNoteRole},
|
| + {"option", kListBoxOptionRole},
|
| + {"presentation", kPresentationalRole},
|
| + {"progressbar", kProgressIndicatorRole},
|
| + {"radio", kRadioButtonRole},
|
| + {"radiogroup", kRadioGroupRole},
|
| + {"region", kRegionRole},
|
| + {"row", kRowRole},
|
| + {"rowheader", kRowHeaderRole},
|
| + {"scrollbar", kScrollBarRole},
|
| + {"search", kSearchRole},
|
| + {"searchbox", kSearchBoxRole},
|
| + {"separator", kSplitterRole},
|
| + {"slider", kSliderRole},
|
| + {"spinbutton", kSpinButtonRole},
|
| + {"status", kStatusRole},
|
| + {"switch", kSwitchRole},
|
| + {"tab", kTabRole},
|
| + {"table", kTableRole},
|
| + {"tablist", kTabListRole},
|
| + {"tabpanel", kTabPanelRole},
|
| + {"term", kTermRole},
|
| + {"text", kStaticTextRole},
|
| + {"textbox", kTextFieldRole},
|
| + {"timer", kTimerRole},
|
| + {"toolbar", kToolbarRole},
|
| + {"tooltip", kUserInterfaceTooltipRole},
|
| + {"tree", kTreeRole},
|
| + {"treegrid", kTreeGridRole},
|
| + {"treeitem", kTreeItemRole}};
|
| +
|
| +struct InternalRoleEntry {
|
| + AccessibilityRole webcore_role;
|
| + const char* internal_role_name;
|
| +};
|
| +
|
| +const InternalRoleEntry kInternalRoles[] = {
|
| + {kUnknownRole, "Unknown"},
|
| + {kAbbrRole, "Abbr"},
|
| + {kAlertDialogRole, "AlertDialog"},
|
| + {kAlertRole, "Alert"},
|
| + {kAnchorRole, "Anchor"},
|
| + {kAnnotationRole, "Annotation"},
|
| + {kApplicationRole, "Application"},
|
| + {kArticleRole, "Article"},
|
| + {kAudioRole, "Audio"},
|
| + {kBannerRole, "Banner"},
|
| + {kBlockquoteRole, "Blockquote"},
|
| + // TODO(nektar): Delete busy_indicator role. It's used nowhere.
|
| + {kBusyIndicatorRole, "BusyIndicator"},
|
| + {kButtonRole, "Button"},
|
| + {kCanvasRole, "Canvas"},
|
| + {kCaptionRole, "Caption"},
|
| + {kCellRole, "Cell"},
|
| + {kCheckBoxRole, "CheckBox"},
|
| + {kColorWellRole, "ColorWell"},
|
| + {kColumnHeaderRole, "ColumnHeader"},
|
| + {kColumnRole, "Column"},
|
| + {kComboBoxRole, "ComboBox"},
|
| + {kComplementaryRole, "Complementary"},
|
| + {kContentInfoRole, "ContentInfo"},
|
| + {kDateRole, "Date"},
|
| + {kDateTimeRole, "DateTime"},
|
| + {kDefinitionRole, "Definition"},
|
| + {kDescriptionListDetailRole, "DescriptionListDetail"},
|
| + {kDescriptionListRole, "DescriptionList"},
|
| + {kDescriptionListTermRole, "DescriptionListTerm"},
|
| + {kDetailsRole, "Details"},
|
| + {kDialogRole, "Dialog"},
|
| + {kDirectoryRole, "Directory"},
|
| + {kDisclosureTriangleRole, "DisclosureTriangle"},
|
| + {kDivRole, "Div"},
|
| + {kDocumentRole, "Document"},
|
| + {kEmbeddedObjectRole, "EmbeddedObject"},
|
| + {kFeedRole, "feed"},
|
| + {kFigcaptionRole, "Figcaption"},
|
| + {kFigureRole, "Figure"},
|
| + {kFooterRole, "Footer"},
|
| + {kFormRole, "Form"},
|
| + {kGridRole, "Grid"},
|
| + {kGroupRole, "Group"},
|
| + {kHeadingRole, "Heading"},
|
| + {kIframePresentationalRole, "IframePresentational"},
|
| + {kIframeRole, "Iframe"},
|
| + {kIgnoredRole, "Ignored"},
|
| + {kImageMapLinkRole, "ImageMapLink"},
|
| + {kImageMapRole, "ImageMap"},
|
| + {kImageRole, "Image"},
|
| + {kInlineTextBoxRole, "InlineTextBox"},
|
| + {kInputTimeRole, "InputTime"},
|
| + {kLabelRole, "Label"},
|
| + {kLegendRole, "Legend"},
|
| + {kLinkRole, "Link"},
|
| + {kLineBreakRole, "LineBreak"},
|
| + {kListBoxOptionRole, "ListBoxOption"},
|
| + {kListBoxRole, "ListBox"},
|
| + {kListItemRole, "ListItem"},
|
| + {kListMarkerRole, "ListMarker"},
|
| + {kListRole, "List"},
|
| + {kLogRole, "Log"},
|
| + {kMainRole, "Main"},
|
| + {kMarkRole, "Mark"},
|
| + {kMarqueeRole, "Marquee"},
|
| + {kMathRole, "Math"},
|
| + {kMenuBarRole, "MenuBar"},
|
| + {kMenuButtonRole, "MenuButton"},
|
| + {kMenuItemRole, "MenuItem"},
|
| + {kMenuItemCheckBoxRole, "MenuItemCheckBox"},
|
| + {kMenuItemRadioRole, "MenuItemRadio"},
|
| + {kMenuListOptionRole, "MenuListOption"},
|
| + {kMenuListPopupRole, "MenuListPopup"},
|
| + {kMenuRole, "Menu"},
|
| + {kMeterRole, "Meter"},
|
| + {kNavigationRole, "Navigation"},
|
| + {kNoneRole, "None"},
|
| + {kNoteRole, "Note"},
|
| + {kOutlineRole, "Outline"},
|
| + {kParagraphRole, "Paragraph"},
|
| + {kPopUpButtonRole, "PopUpButton"},
|
| + {kPreRole, "Pre"},
|
| + {kPresentationalRole, "Presentational"},
|
| + {kProgressIndicatorRole, "ProgressIndicator"},
|
| + {kRadioButtonRole, "RadioButton"},
|
| + {kRadioGroupRole, "RadioGroup"},
|
| + {kRegionRole, "Region"},
|
| + {kRootWebAreaRole, "RootWebArea"},
|
| + {kRowHeaderRole, "RowHeader"},
|
| + {kRowRole, "Row"},
|
| + {kRubyRole, "Ruby"},
|
| + {kRulerRole, "Ruler"},
|
| + {kSVGRootRole, "SVGRoot"},
|
| + {kScrollAreaRole, "ScrollArea"},
|
| + {kScrollBarRole, "ScrollBar"},
|
| + {kSeamlessWebAreaRole, "SeamlessWebArea"},
|
| + {kSearchRole, "Search"},
|
| + {kSearchBoxRole, "SearchBox"},
|
| + {kSliderRole, "Slider"},
|
| + {kSliderThumbRole, "SliderThumb"},
|
| + {kSpinButtonPartRole, "SpinButtonPart"},
|
| + {kSpinButtonRole, "SpinButton"},
|
| + {kSplitterRole, "Splitter"},
|
| + {kStaticTextRole, "StaticText"},
|
| + {kStatusRole, "Status"},
|
| + {kSwitchRole, "Switch"},
|
| + {kTabGroupRole, "TabGroup"},
|
| + {kTabListRole, "TabList"},
|
| + {kTabPanelRole, "TabPanel"},
|
| + {kTabRole, "Tab"},
|
| + {kTableHeaderContainerRole, "TableHeaderContainer"},
|
| + {kTableRole, "Table"},
|
| + {kTermRole, "Term"},
|
| + {kTextFieldRole, "TextField"},
|
| + {kTimeRole, "Time"},
|
| + {kTimerRole, "Timer"},
|
| + {kToggleButtonRole, "ToggleButton"},
|
| + {kToolbarRole, "Toolbar"},
|
| + {kTreeGridRole, "TreeGrid"},
|
| + {kTreeItemRole, "TreeItem"},
|
| + {kTreeRole, "Tree"},
|
| + {kUserInterfaceTooltipRole, "UserInterfaceTooltip"},
|
| + {kVideoRole, "Video"},
|
| + {kWebAreaRole, "WebArea"},
|
| + {kWindowRole, "Window"}};
|
| +
|
| +static_assert(WTF_ARRAY_LENGTH(kInternalRoles) == kNumRoles,
|
| + "Not all internal roles have an entry in internalRoles array");
|
| +
|
| +// Roles which we need to map in the other direction
|
| +const RoleEntry kReverseRoles[] = {
|
| + {"button", kToggleButtonRole}, {"combobox", kPopUpButtonRole},
|
| + {"contentinfo", kFooterRole}, {"menuitem", kMenuButtonRole},
|
| + {"menuitem", kMenuListOptionRole}, {"progressbar", kMeterRole},
|
| + {"textbox", kTextFieldRole}};
|
| +
|
| +static ARIARoleMap* CreateARIARoleMap() {
|
| + ARIARoleMap* role_map = new ARIARoleMap;
|
| +
|
| + for (size_t i = 0; i < WTF_ARRAY_LENGTH(kRoles); ++i)
|
| + role_map->Set(String(kRoles[i].aria_role), kRoles[i].webcore_role);
|
| + return role_map;
|
| +}
|
| +
|
| +static Vector<AtomicString>* CreateRoleNameVector() {
|
| + Vector<AtomicString>* role_name_vector = new Vector<AtomicString>(kNumRoles);
|
| + for (int i = 0; i < kNumRoles; i++)
|
| + (*role_name_vector)[i] = g_null_atom;
|
| +
|
| + for (size_t i = 0; i < WTF_ARRAY_LENGTH(kRoles); ++i)
|
| + (*role_name_vector)[kRoles[i].webcore_role] =
|
| + AtomicString(kRoles[i].aria_role);
|
| +
|
| + for (size_t i = 0; i < WTF_ARRAY_LENGTH(kReverseRoles); ++i)
|
| + (*role_name_vector)[kReverseRoles[i].webcore_role] =
|
| + AtomicString(kReverseRoles[i].aria_role);
|
| +
|
| + return role_name_vector;
|
| +}
|
| +
|
| +static Vector<AtomicString>* CreateInternalRoleNameVector() {
|
| + Vector<AtomicString>* internal_role_name_vector =
|
| + new Vector<AtomicString>(kNumRoles);
|
| + for (size_t i = 0; i < WTF_ARRAY_LENGTH(kInternalRoles); i++)
|
| + (*internal_role_name_vector)[kInternalRoles[i].webcore_role] =
|
| + AtomicString(kInternalRoles[i].internal_role_name);
|
| +
|
| + return internal_role_name_vector;
|
| +}
|
| +
|
| +const char* g_aria_widgets[] = {
|
| + // From http://www.w3.org/TR/wai-aria/roles#widget_roles
|
| + "alert", "alertdialog", "button", "checkbox", "dialog", "gridcell", "link",
|
| + "log", "marquee", "menuitem", "menuitemcheckbox", "menuitemradio", "option",
|
| + "progressbar", "radio", "scrollbar", "slider", "spinbutton", "status",
|
| + "tab", "tabpanel", "textbox", "timer", "tooltip", "treeitem",
|
| + // Composite user interface widgets.
|
| + // This list is also from the w3.org site referenced above.
|
| + "combobox", "grid", "listbox", "menu", "menubar", "radiogroup", "tablist",
|
| + "tree", "treegrid"};
|
| +
|
| +static ARIAWidgetSet* CreateARIARoleWidgetSet() {
|
| + ARIAWidgetSet* widget_set = new HashSet<String, CaseFoldingHash>();
|
| + for (size_t i = 0; i < WTF_ARRAY_LENGTH(g_aria_widgets); ++i)
|
| + widget_set->insert(String(g_aria_widgets[i]));
|
| + return widget_set;
|
| +}
|
| +
|
| +const char* g_aria_interactive_widget_attributes[] = {
|
| + // These attributes implicitly indicate the given widget is interactive.
|
| + // From http://www.w3.org/TR/wai-aria/states_and_properties#attrs_widgets
|
| + "aria-activedescendant", "aria-checked", "aria-controls",
|
| + "aria-disabled", // If it's disabled, it can be made interactive.
|
| + "aria-expanded", "aria-haspopup", "aria-multiselectable",
|
| + "aria-pressed", "aria-required", "aria-selected"};
|
| +
|
| +HTMLDialogElement* GetActiveDialogElement(Node* node) {
|
| + return node->GetDocument().ActiveModalDialog();
|
| +}
|
| +
|
| +} // namespace
|
| +
|
| +unsigned AXObject::number_of_live_ax_objects_ = 0;
|
| +
|
| +AXObject::AXObject(AXObjectCacheImpl& ax_object_cache)
|
| + : id_(0),
|
| + have_children_(false),
|
| + role_(kUnknownRole),
|
| + last_known_is_ignored_value_(kDefaultBehavior),
|
| + explicit_container_id_(0),
|
| + parent_(nullptr),
|
| + last_modification_count_(-1),
|
| + cached_is_ignored_(false),
|
| + cached_is_inert_or_aria_hidden_(false),
|
| + cached_is_descendant_of_leaf_node_(false),
|
| + cached_is_descendant_of_disabled_node_(false),
|
| + cached_has_inherited_presentational_role_(false),
|
| + cached_is_presentational_child_(false),
|
| + cached_ancestor_exposes_active_descendant_(false),
|
| + cached_live_region_root_(nullptr),
|
| + ax_object_cache_(&ax_object_cache) {
|
| + ++number_of_live_ax_objects_;
|
| +}
|
| +
|
| +AXObject::~AXObject() {
|
| + DCHECK(IsDetached());
|
| + --number_of_live_ax_objects_;
|
| +}
|
| +
|
| +void AXObject::Detach() {
|
| + // Clear any children and call detachFromParent on them so that
|
| + // no children are left with dangling pointers to their parent.
|
| + ClearChildren();
|
| +
|
| + ax_object_cache_ = nullptr;
|
| +}
|
| +
|
| +bool AXObject::IsDetached() const {
|
| + return !ax_object_cache_;
|
| +}
|
| +
|
| +const AtomicString& AXObject::GetAOMPropertyOrARIAAttribute(
|
| + AOMStringProperty property) const {
|
| + Node* node = this->GetNode();
|
| + if (!node || !node->IsElementNode())
|
| + return g_null_atom;
|
| +
|
| + return AccessibleNode::GetPropertyOrARIAAttribute(ToElement(node), property);
|
| +}
|
| +
|
| +bool AXObject::IsARIATextControl() const {
|
| + return AriaRoleAttribute() == kTextFieldRole ||
|
| + AriaRoleAttribute() == kSearchBoxRole ||
|
| + AriaRoleAttribute() == kComboBoxRole;
|
| +}
|
| +
|
| +bool AXObject::IsButton() const {
|
| + AccessibilityRole role = RoleValue();
|
| +
|
| + return role == kButtonRole || role == kPopUpButtonRole ||
|
| + role == kToggleButtonRole;
|
| +}
|
| +
|
| +bool AXObject::IsCheckable() const {
|
| + switch (RoleValue()) {
|
| + case kCheckBoxRole:
|
| + case kMenuItemCheckBoxRole:
|
| + case kMenuItemRadioRole:
|
| + case kRadioButtonRole:
|
| + case kSwitchRole:
|
| + return true;
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +// Why this is here instead of AXNodeObject:
|
| +// Because an AXMenuListOption (<option>) can
|
| +// have an ARIA role of menuitemcheckbox/menuitemradio
|
| +// yet does not inherit from AXNodeObject
|
| +AccessibilityButtonState AXObject::CheckedState() const {
|
| + if (!IsCheckable())
|
| + return kButtonStateOff;
|
| +
|
| + const AtomicString& checkedAttribute =
|
| + GetAOMPropertyOrARIAAttribute(AOMStringProperty::kChecked);
|
| + if (checkedAttribute) {
|
| + if (EqualIgnoringASCIICase(checkedAttribute, "true"))
|
| + return kButtonStateOn;
|
| +
|
| + if (EqualIgnoringASCIICase(checkedAttribute, "mixed")) {
|
| + // Only checkboxes and radios should support the mixed state.
|
| + const AccessibilityRole role = RoleValue();
|
| + if (role == kCheckBoxRole || role == kMenuItemCheckBoxRole ||
|
| + role == kRadioButtonRole || role == kMenuItemRadioRole)
|
| + return kButtonStateMixed;
|
| + }
|
| +
|
| + return kButtonStateOff;
|
| + }
|
| +
|
| + const Node* node = this->GetNode();
|
| + if (!node)
|
| + return kButtonStateOff;
|
| +
|
| + if (IsNativeInputInMixedState(node))
|
| + return kButtonStateMixed;
|
| +
|
| + if (isHTMLInputElement(*node) &&
|
| + toHTMLInputElement(*node).ShouldAppearChecked()) {
|
| + return kButtonStateOn;
|
| + }
|
| +
|
| + return kButtonStateOff;
|
| +}
|
| +
|
| +bool AXObject::IsNativeInputInMixedState(const Node* node) {
|
| + if (!isHTMLInputElement(node))
|
| + return false;
|
| +
|
| + const HTMLInputElement* input = toHTMLInputElement(node);
|
| + const auto inputType = input->type();
|
| + if (inputType != InputTypeNames::checkbox &&
|
| + inputType != InputTypeNames::radio)
|
| + return false;
|
| + return input->ShouldAppearIndeterminate();
|
| +}
|
| +
|
| +bool AXObject::IsLandmarkRelated() const {
|
| + switch (RoleValue()) {
|
| + case kApplicationRole:
|
| + case kArticleRole:
|
| + case kBannerRole:
|
| + case kComplementaryRole:
|
| + case kContentInfoRole:
|
| + case kFooterRole:
|
| + case kFormRole:
|
| + case kMainRole:
|
| + case kNavigationRole:
|
| + case kRegionRole:
|
| + case kSearchRole:
|
| + return true;
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +bool AXObject::IsMenuRelated() const {
|
| + switch (RoleValue()) {
|
| + case kMenuRole:
|
| + case kMenuBarRole:
|
| + case kMenuButtonRole:
|
| + case kMenuItemRole:
|
| + case kMenuItemCheckBoxRole:
|
| + case kMenuItemRadioRole:
|
| + return true;
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +bool AXObject::IsPasswordFieldAndShouldHideValue() const {
|
| + Settings* settings = GetDocument()->GetSettings();
|
| + if (!settings || settings->GetAccessibilityPasswordValuesEnabled())
|
| + return false;
|
| +
|
| + return IsPasswordField();
|
| +}
|
| +
|
| +bool AXObject::IsClickable() const {
|
| + switch (RoleValue()) {
|
| + case kButtonRole:
|
| + case kCheckBoxRole:
|
| + case kColorWellRole:
|
| + case kComboBoxRole:
|
| + case kImageMapLinkRole:
|
| + case kLinkRole:
|
| + case kListBoxOptionRole:
|
| + case kMenuButtonRole:
|
| + case kPopUpButtonRole:
|
| + case kRadioButtonRole:
|
| + case kSpinButtonRole:
|
| + case kTabRole:
|
| + case kTextFieldRole:
|
| + case kToggleButtonRole:
|
| + return true;
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +bool AXObject::AccessibilityIsIgnored() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_is_ignored_;
|
| +}
|
| +
|
| +void AXObject::UpdateCachedAttributeValuesIfNeeded() const {
|
| + if (IsDetached())
|
| + return;
|
| +
|
| + AXObjectCacheImpl& cache = AxObjectCache();
|
| +
|
| + if (cache.ModificationCount() == last_modification_count_)
|
| + return;
|
| +
|
| + last_modification_count_ = cache.ModificationCount();
|
| + cached_background_color_ = ComputeBackgroundColor();
|
| + cached_is_inert_or_aria_hidden_ = ComputeIsInertOrAriaHidden();
|
| + cached_is_descendant_of_leaf_node_ = (LeafNodeAncestor() != 0);
|
| + cached_is_descendant_of_disabled_node_ = (DisabledAncestor() != 0);
|
| + cached_has_inherited_presentational_role_ =
|
| + (InheritsPresentationalRoleFrom() != 0);
|
| + cached_is_presentational_child_ =
|
| + (AncestorForWhichThisIsAPresentationalChild() != 0);
|
| + cached_is_ignored_ = ComputeAccessibilityIsIgnored();
|
| + cached_live_region_root_ =
|
| + IsLiveRegion()
|
| + ? const_cast<AXObject*>(this)
|
| + : (ParentObjectIfExists() ? ParentObjectIfExists()->LiveRegionRoot()
|
| + : 0);
|
| + cached_ancestor_exposes_active_descendant_ =
|
| + ComputeAncestorExposesActiveDescendant();
|
| +}
|
| +
|
| +bool AXObject::AccessibilityIsIgnoredByDefault(
|
| + IgnoredReasons* ignored_reasons) const {
|
| + return DefaultObjectInclusion(ignored_reasons) == kIgnoreObject;
|
| +}
|
| +
|
| +AXObjectInclusion AXObject::AccessibilityPlatformIncludesObject() const {
|
| + if (IsMenuListPopup() || IsMenuListOption())
|
| + return kIncludeObject;
|
| +
|
| + return kDefaultBehavior;
|
| +}
|
| +
|
| +AXObjectInclusion AXObject::DefaultObjectInclusion(
|
| + IgnoredReasons* ignored_reasons) const {
|
| + if (IsInertOrAriaHidden()) {
|
| + if (ignored_reasons)
|
| + ComputeIsInertOrAriaHidden(ignored_reasons);
|
| + return kIgnoreObject;
|
| + }
|
| +
|
| + if (IsPresentationalChild()) {
|
| + if (ignored_reasons) {
|
| + AXObject* ancestor = AncestorForWhichThisIsAPresentationalChild();
|
| + ignored_reasons->push_back(
|
| + IgnoredReason(kAXAncestorDisallowsChild, ancestor));
|
| + }
|
| + return kIgnoreObject;
|
| + }
|
| +
|
| + return AccessibilityPlatformIncludesObject();
|
| +}
|
| +
|
| +bool AXObject::IsInertOrAriaHidden() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_is_inert_or_aria_hidden_;
|
| +}
|
| +
|
| +bool AXObject::ComputeIsInertOrAriaHidden(
|
| + IgnoredReasons* ignored_reasons) const {
|
| + if (GetNode()) {
|
| + if (GetNode()->IsInert()) {
|
| + if (ignored_reasons) {
|
| + HTMLDialogElement* dialog = GetActiveDialogElement(GetNode());
|
| + if (dialog) {
|
| + AXObject* dialog_object = AxObjectCache().GetOrCreate(dialog);
|
| + if (dialog_object)
|
| + ignored_reasons->push_back(
|
| + IgnoredReason(kAXActiveModalDialog, dialog_object));
|
| + else
|
| + ignored_reasons->push_back(IgnoredReason(kAXInert));
|
| + } else {
|
| + // TODO(aboxhall): handle inert attribute if it eventuates
|
| + ignored_reasons->push_back(IgnoredReason(kAXInert));
|
| + }
|
| + }
|
| + return true;
|
| + }
|
| + } else {
|
| + AXObject* parent = ParentObject();
|
| + if (parent && parent->IsInertOrAriaHidden()) {
|
| + if (ignored_reasons)
|
| + parent->ComputeIsInertOrAriaHidden(ignored_reasons);
|
| + return true;
|
| + }
|
| + }
|
| +
|
| + const AXObject* hidden_root = AriaHiddenRoot();
|
| + if (hidden_root) {
|
| + if (ignored_reasons) {
|
| + if (hidden_root == this)
|
| + ignored_reasons->push_back(IgnoredReason(kAXAriaHidden));
|
| + else
|
| + ignored_reasons->push_back(
|
| + IgnoredReason(kAXAriaHiddenRoot, hidden_root));
|
| + }
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +bool AXObject::IsDescendantOfLeafNode() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_is_descendant_of_leaf_node_;
|
| +}
|
| +
|
| +AXObject* AXObject::LeafNodeAncestor() const {
|
| + if (AXObject* parent = ParentObject()) {
|
| + if (!parent->CanHaveChildren())
|
| + return parent;
|
| +
|
| + return parent->LeafNodeAncestor();
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +const AXObject* AXObject::AriaHiddenRoot() const {
|
| + for (const AXObject* object = this; object; object = object->ParentObject()) {
|
| + if (EqualIgnoringASCIICase(object->GetAttribute(aria_hiddenAttr), "true"))
|
| + return object;
|
| + }
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +bool AXObject::IsDescendantOfDisabledNode() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_is_descendant_of_disabled_node_;
|
| +}
|
| +
|
| +const AXObject* AXObject::DisabledAncestor() const {
|
| + const AtomicString& disabled = GetAttribute(aria_disabledAttr);
|
| + if (EqualIgnoringASCIICase(disabled, "true"))
|
| + return this;
|
| + if (EqualIgnoringASCIICase(disabled, "false"))
|
| + return 0;
|
| +
|
| + if (AXObject* parent = ParentObject())
|
| + return parent->DisabledAncestor();
|
| +
|
| + return 0;
|
| +}
|
| +
|
| +bool AXObject::LastKnownIsIgnoredValue() {
|
| + if (last_known_is_ignored_value_ == kDefaultBehavior)
|
| + last_known_is_ignored_value_ =
|
| + AccessibilityIsIgnored() ? kIgnoreObject : kIncludeObject;
|
| +
|
| + return last_known_is_ignored_value_ == kIgnoreObject;
|
| +}
|
| +
|
| +void AXObject::SetLastKnownIsIgnoredValue(bool is_ignored) {
|
| + last_known_is_ignored_value_ = is_ignored ? kIgnoreObject : kIncludeObject;
|
| +}
|
| +
|
| +bool AXObject::HasInheritedPresentationalRole() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_has_inherited_presentational_role_;
|
| +}
|
| +
|
| +bool AXObject::IsPresentationalChild() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_is_presentational_child_;
|
| +}
|
| +
|
| +bool AXObject::AncestorExposesActiveDescendant() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_ancestor_exposes_active_descendant_;
|
| +}
|
| +
|
| +bool AXObject::ComputeAncestorExposesActiveDescendant() const {
|
| + const AXObject* parent = ParentObjectUnignored();
|
| + if (!parent)
|
| + return false;
|
| +
|
| + if (parent->SupportsActiveDescendant() &&
|
| + !parent->GetAttribute(aria_activedescendantAttr).IsEmpty()) {
|
| + return true;
|
| + }
|
| +
|
| + return parent->AncestorExposesActiveDescendant();
|
| +}
|
| +
|
| +// Simplify whitespace, but preserve a single leading and trailing whitespace
|
| +// character if it's present.
|
| +// static
|
| +String AXObject::CollapseWhitespace(const String& str) {
|
| + StringBuilder result;
|
| + if (!str.IsEmpty() && IsHTMLSpace<UChar>(str[0]))
|
| + result.Append(' ');
|
| + result.Append(str.SimplifyWhiteSpace(IsHTMLSpace<UChar>));
|
| + if (!str.IsEmpty() && IsHTMLSpace<UChar>(str[str.length() - 1]))
|
| + result.Append(' ');
|
| + return result.ToString();
|
| +}
|
| +
|
| +String AXObject::ComputedName() const {
|
| + AXNameFrom name_from;
|
| + AXObject::AXObjectVector name_objects;
|
| + return GetName(name_from, &name_objects);
|
| +}
|
| +
|
| +String AXObject::GetName(AXNameFrom& name_from,
|
| + AXObject::AXObjectVector* name_objects) const {
|
| + HeapHashSet<Member<const AXObject>> visited;
|
| + AXRelatedObjectVector related_objects;
|
| + String text = TextAlternative(false, false, visited, name_from,
|
| + &related_objects, nullptr);
|
| +
|
| + AccessibilityRole role = RoleValue();
|
| + if (!GetNode() || (!isHTMLBRElement(GetNode()) && role != kStaticTextRole &&
|
| + role != kInlineTextBoxRole))
|
| + text = CollapseWhitespace(text);
|
| +
|
| + if (name_objects) {
|
| + name_objects->clear();
|
| + for (size_t i = 0; i < related_objects.size(); i++)
|
| + name_objects->push_back(related_objects[i]->object);
|
| + }
|
| +
|
| + return text;
|
| +}
|
| +
|
| +String AXObject::GetName(NameSources* name_sources) const {
|
| + AXObjectSet visited;
|
| + AXNameFrom tmp_name_from;
|
| + AXRelatedObjectVector tmp_related_objects;
|
| + String text = TextAlternative(false, false, visited, tmp_name_from,
|
| + &tmp_related_objects, name_sources);
|
| + text = text.SimplifyWhiteSpace(IsHTMLSpace<UChar>);
|
| + return text;
|
| +}
|
| +
|
| +String AXObject::RecursiveTextAlternative(const AXObject& ax_obj,
|
| + bool in_aria_labelled_by_traversal,
|
| + AXObjectSet& visited) {
|
| + if (visited.Contains(&ax_obj) && !in_aria_labelled_by_traversal)
|
| + return String();
|
| +
|
| + AXNameFrom tmp_name_from;
|
| + return ax_obj.TextAlternative(true, in_aria_labelled_by_traversal, visited,
|
| + tmp_name_from, nullptr, nullptr);
|
| +}
|
| +
|
| +bool AXObject::IsHiddenForTextAlternativeCalculation() const {
|
| + if (EqualIgnoringASCIICase(GetAttribute(aria_hiddenAttr), "false"))
|
| + return false;
|
| +
|
| + if (GetLayoutObject())
|
| + return GetLayoutObject()->Style()->Visibility() != EVisibility::kVisible;
|
| +
|
| + // This is an obscure corner case: if a node has no LayoutObject, that means
|
| + // it's not rendered, but we still may be exploring it as part of a text
|
| + // alternative calculation, for example if it was explicitly referenced by
|
| + // aria-labelledby. So we need to explicitly call the style resolver to check
|
| + // whether it's invisible or display:none, rather than relying on the style
|
| + // cached in the LayoutObject.
|
| + Document* document = GetDocument();
|
| + if (!document || !document->GetFrame())
|
| + return false;
|
| + if (Node* node = GetNode()) {
|
| + if (node->isConnected() && node->IsElementNode()) {
|
| + RefPtr<ComputedStyle> style =
|
| + document->EnsureStyleResolver().StyleForElement(ToElement(node));
|
| + return style->Display() == EDisplay::kNone ||
|
| + style->Visibility() != EVisibility::kVisible;
|
| + }
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +String AXObject::AriaTextAlternative(bool recursive,
|
| + bool in_aria_labelled_by_traversal,
|
| + AXObjectSet& visited,
|
| + AXNameFrom& name_from,
|
| + AXRelatedObjectVector* related_objects,
|
| + NameSources* name_sources,
|
| + bool* found_text_alternative) const {
|
| + String text_alternative;
|
| + bool already_visited = visited.Contains(this);
|
| + visited.insert(this);
|
| +
|
| + // Step 2A from: http://www.w3.org/TR/accname-aam-1.1
|
| + // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
|
| + if (!in_aria_labelled_by_traversal &&
|
| + IsHiddenForTextAlternativeCalculation()) {
|
| + *found_text_alternative = true;
|
| + return String();
|
| + }
|
| +
|
| + // Step 2B from: http://www.w3.org/TR/accname-aam-1.1
|
| + // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
|
| + if (!in_aria_labelled_by_traversal && !already_visited) {
|
| + const QualifiedName& attr =
|
| + HasAttribute(aria_labeledbyAttr) && !HasAttribute(aria_labelledbyAttr)
|
| + ? aria_labeledbyAttr
|
| + : aria_labelledbyAttr;
|
| + name_from = kAXNameFromRelatedElement;
|
| + if (name_sources) {
|
| + name_sources->push_back(NameSource(*found_text_alternative, attr));
|
| + name_sources->back().type = name_from;
|
| + }
|
| +
|
| + const AtomicString& aria_labelledby = GetAttribute(attr);
|
| + if (!aria_labelledby.IsNull()) {
|
| + if (name_sources)
|
| + name_sources->back().attribute_value = aria_labelledby;
|
| +
|
| + // Operate on a copy of |visited| so that if |nameSources| is not null,
|
| + // the set of visited objects is preserved unmodified for future
|
| + // calculations.
|
| + AXObjectSet visited_copy = visited;
|
| + text_alternative = TextFromAriaLabelledby(visited_copy, related_objects);
|
| + if (!text_alternative.IsNull()) {
|
| + if (name_sources) {
|
| + NameSource& source = name_sources->back();
|
| + source.type = name_from;
|
| + source.related_objects = *related_objects;
|
| + source.text = text_alternative;
|
| + *found_text_alternative = true;
|
| + } else {
|
| + *found_text_alternative = true;
|
| + return text_alternative;
|
| + }
|
| + } else if (name_sources) {
|
| + name_sources->back().invalid = true;
|
| + }
|
| + }
|
| + }
|
| +
|
| + // Step 2C from: http://www.w3.org/TR/accname-aam-1.1
|
| + // If you change this logic, update AXNodeObject::nameFromLabelElement, too.
|
| + name_from = kAXNameFromAttribute;
|
| + if (name_sources) {
|
| + name_sources->push_back(
|
| + NameSource(*found_text_alternative, aria_labelAttr));
|
| + name_sources->back().type = name_from;
|
| + }
|
| + const AtomicString& aria_label =
|
| + GetAOMPropertyOrARIAAttribute(AOMStringProperty::kLabel);
|
| + if (!aria_label.IsEmpty()) {
|
| + text_alternative = aria_label;
|
| +
|
| + if (name_sources) {
|
| + NameSource& source = name_sources->back();
|
| + source.text = text_alternative;
|
| + source.attribute_value = aria_label;
|
| + *found_text_alternative = true;
|
| + } else {
|
| + *found_text_alternative = true;
|
| + return text_alternative;
|
| + }
|
| + }
|
| +
|
| + return text_alternative;
|
| +}
|
| +
|
| +String AXObject::TextFromElements(
|
| + bool in_aria_labelledby_traversal,
|
| + AXObjectSet& visited,
|
| + HeapVector<Member<Element>>& elements,
|
| + AXRelatedObjectVector* related_objects) const {
|
| + StringBuilder accumulated_text;
|
| + bool found_valid_element = false;
|
| + AXRelatedObjectVector local_related_objects;
|
| +
|
| + for (const auto& element : elements) {
|
| + AXObject* ax_element = AxObjectCache().GetOrCreate(element);
|
| + if (ax_element) {
|
| + found_valid_element = true;
|
| +
|
| + String result = RecursiveTextAlternative(
|
| + *ax_element, in_aria_labelledby_traversal, visited);
|
| + local_related_objects.push_back(
|
| + new NameSourceRelatedObject(ax_element, result));
|
| + if (!result.IsEmpty()) {
|
| + if (!accumulated_text.IsEmpty())
|
| + accumulated_text.Append(' ');
|
| + accumulated_text.Append(result);
|
| + }
|
| + }
|
| + }
|
| + if (!found_valid_element)
|
| + return String();
|
| + if (related_objects)
|
| + *related_objects = local_related_objects;
|
| + return accumulated_text.ToString();
|
| +}
|
| +
|
| +void AXObject::TokenVectorFromAttribute(Vector<String>& tokens,
|
| + const QualifiedName& attribute) const {
|
| + Node* node = this->GetNode();
|
| + if (!node || !node->IsElementNode())
|
| + return;
|
| +
|
| + String attribute_value = GetAttribute(attribute).GetString();
|
| + if (attribute_value.IsEmpty())
|
| + return;
|
| +
|
| + attribute_value.SimplifyWhiteSpace();
|
| + attribute_value.Split(' ', tokens);
|
| +}
|
| +
|
| +void AXObject::ElementsFromAttribute(HeapVector<Member<Element>>& elements,
|
| + const QualifiedName& attribute) const {
|
| + Vector<String> ids;
|
| + TokenVectorFromAttribute(ids, attribute);
|
| + if (ids.IsEmpty())
|
| + return;
|
| +
|
| + TreeScope& scope = GetNode()->GetTreeScope();
|
| + for (const auto& id : ids) {
|
| + if (Element* id_element = scope.getElementById(AtomicString(id)))
|
| + elements.push_back(id_element);
|
| + }
|
| +}
|
| +
|
| +void AXObject::AriaLabelledbyElementVector(
|
| + HeapVector<Member<Element>>& elements) const {
|
| + // Try both spellings, but prefer aria-labelledby, which is the official spec.
|
| + ElementsFromAttribute(elements, aria_labelledbyAttr);
|
| + if (!elements.size())
|
| + ElementsFromAttribute(elements, aria_labeledbyAttr);
|
| +}
|
| +
|
| +String AXObject::TextFromAriaLabelledby(
|
| + AXObjectSet& visited,
|
| + AXRelatedObjectVector* related_objects) const {
|
| + HeapVector<Member<Element>> elements;
|
| + AriaLabelledbyElementVector(elements);
|
| + return TextFromElements(true, visited, elements, related_objects);
|
| +}
|
| +
|
| +String AXObject::TextFromAriaDescribedby(
|
| + AXRelatedObjectVector* related_objects) const {
|
| + AXObjectSet visited;
|
| + HeapVector<Member<Element>> elements;
|
| + ElementsFromAttribute(elements, aria_describedbyAttr);
|
| + return TextFromElements(true, visited, elements, related_objects);
|
| +}
|
| +
|
| +RGBA32 AXObject::BackgroundColor() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_background_color_;
|
| +}
|
| +
|
| +AccessibilityOrientation AXObject::Orientation() const {
|
| + // In ARIA 1.1, the default value for aria-orientation changed from
|
| + // horizontal to undefined.
|
| + return kAccessibilityOrientationUndefined;
|
| +}
|
| +
|
| +AXSupportedAction AXObject::Action() const {
|
| + if (!ActionElement())
|
| + return AXSupportedAction::kNone;
|
| +
|
| + switch (RoleValue()) {
|
| + case kButtonRole:
|
| + case kToggleButtonRole:
|
| + return AXSupportedAction::kPress;
|
| + case kTextFieldRole:
|
| + return AXSupportedAction::kActivate;
|
| + case kRadioButtonRole:
|
| + return AXSupportedAction::kSelect;
|
| + case kCheckBoxRole:
|
| + case kSwitchRole:
|
| + return CheckedState() == kButtonStateOff ? AXSupportedAction::kCheck
|
| + : AXSupportedAction::kUncheck;
|
| + case kLinkRole:
|
| + return AXSupportedAction::kJump;
|
| + case kPopUpButtonRole:
|
| + return AXSupportedAction::kOpen;
|
| + default:
|
| + return AXSupportedAction::kClick;
|
| + }
|
| +}
|
| +
|
| +bool AXObject::IsMultiline() const {
|
| + Node* node = this->GetNode();
|
| + if (!node)
|
| + return false;
|
| +
|
| + if (isHTMLTextAreaElement(*node))
|
| + return true;
|
| +
|
| + if (HasEditableStyle(*node))
|
| + return true;
|
| +
|
| + if (!IsNativeTextControl() && !IsNonNativeTextControl())
|
| + return false;
|
| +
|
| + return EqualIgnoringASCIICase(GetAttribute(aria_multilineAttr), "true");
|
| +}
|
| +
|
| +bool AXObject::AriaPressedIsPresent() const {
|
| + return !GetAttribute(aria_pressedAttr).IsEmpty();
|
| +}
|
| +
|
| +bool AXObject::SupportsActiveDescendant() const {
|
| + // According to the ARIA Spec, all ARIA composite widgets, ARIA text boxes
|
| + // and ARIA groups should be able to expose an active descendant.
|
| + // Implicitly, <input> and <textarea> elements should also have this ability.
|
| + switch (AriaRoleAttribute()) {
|
| + case kComboBoxRole:
|
| + case kGridRole:
|
| + case kGroupRole:
|
| + case kListBoxRole:
|
| + case kMenuRole:
|
| + case kMenuBarRole:
|
| + case kRadioGroupRole:
|
| + case kRowRole:
|
| + case kSearchBoxRole:
|
| + case kTabListRole:
|
| + case kTextFieldRole:
|
| + case kToolbarRole:
|
| + case kTreeRole:
|
| + case kTreeGridRole:
|
| + return true;
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +bool AXObject::SupportsARIAAttributes() const {
|
| + return IsLiveRegion() || SupportsARIADragging() || SupportsARIADropping() ||
|
| + SupportsARIAFlowTo() || SupportsARIAOwns() ||
|
| + HasAttribute(aria_labelAttr);
|
| +}
|
| +
|
| +bool AXObject::SupportsRangeValue() const {
|
| + return IsProgressIndicator() || IsMeter() || IsSlider() || IsScrollbar() ||
|
| + IsSpinButton();
|
| +}
|
| +
|
| +bool AXObject::SupportsSetSizeAndPosInSet() const {
|
| + AXObject* parent = ParentObject();
|
| + if (!parent)
|
| + return false;
|
| +
|
| + int role = RoleValue();
|
| + int parent_role = parent->RoleValue();
|
| +
|
| + if ((role == kListBoxOptionRole && parent_role == kListBoxRole) ||
|
| + (role == kListItemRole && parent_role == kListRole) ||
|
| + (role == kMenuItemRole && parent_role == kMenuRole) ||
|
| + (role == kRadioButtonRole) ||
|
| + (role == kTabRole && parent_role == kTabListRole) ||
|
| + (role == kTreeItemRole && parent_role == kTreeRole) ||
|
| + (role == kTreeItemRole && parent_role == kGroupRole)) {
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +int AXObject::IndexInParent() const {
|
| + if (!ParentObject())
|
| + return 0;
|
| +
|
| + const auto& siblings = ParentObject()->Children();
|
| + int child_count = siblings.size();
|
| +
|
| + for (int index = 0; index < child_count; ++index) {
|
| + if (siblings[index].Get() == this) {
|
| + return index;
|
| + }
|
| + }
|
| + return 0;
|
| +}
|
| +
|
| +bool AXObject::IsLiveRegion() const {
|
| + const AtomicString& live_region = LiveRegionStatus();
|
| + return EqualIgnoringASCIICase(live_region, "polite") ||
|
| + EqualIgnoringASCIICase(live_region, "assertive");
|
| +}
|
| +
|
| +AXObject* AXObject::LiveRegionRoot() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_live_region_root_;
|
| +}
|
| +
|
| +const AtomicString& AXObject::ContainerLiveRegionStatus() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_live_region_root_ ? cached_live_region_root_->LiveRegionStatus()
|
| + : g_null_atom;
|
| +}
|
| +
|
| +const AtomicString& AXObject::ContainerLiveRegionRelevant() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_live_region_root_
|
| + ? cached_live_region_root_->LiveRegionRelevant()
|
| + : g_null_atom;
|
| +}
|
| +
|
| +bool AXObject::ContainerLiveRegionAtomic() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_live_region_root_ &&
|
| + cached_live_region_root_->LiveRegionAtomic();
|
| +}
|
| +
|
| +bool AXObject::ContainerLiveRegionBusy() const {
|
| + UpdateCachedAttributeValuesIfNeeded();
|
| + return cached_live_region_root_ && cached_live_region_root_->LiveRegionBusy();
|
| +}
|
| +
|
| +AXObject* AXObject::ElementAccessibilityHitTest(const IntPoint& point) const {
|
| + // Check if there are any mock elements that need to be handled.
|
| + for (const auto& child : children_) {
|
| + if (child->IsMockObject() &&
|
| + child->GetBoundsInFrameCoordinates().Contains(point))
|
| + return child->ElementAccessibilityHitTest(point);
|
| + }
|
| +
|
| + return const_cast<AXObject*>(this);
|
| +}
|
| +
|
| +const AXObject::AXObjectVector& AXObject::Children() {
|
| + UpdateChildrenIfNecessary();
|
| +
|
| + return children_;
|
| +}
|
| +
|
| +AXObject* AXObject::ParentObject() const {
|
| + if (IsDetached())
|
| + return 0;
|
| +
|
| + if (parent_)
|
| + return parent_;
|
| +
|
| + if (AxObjectCache().IsAriaOwned(this))
|
| + return AxObjectCache().GetAriaOwnedParent(this);
|
| +
|
| + return ComputeParent();
|
| +}
|
| +
|
| +AXObject* AXObject::ParentObjectIfExists() const {
|
| + if (IsDetached())
|
| + return 0;
|
| +
|
| + if (parent_)
|
| + return parent_;
|
| +
|
| + return ComputeParentIfExists();
|
| +}
|
| +
|
| +AXObject* AXObject::ParentObjectUnignored() const {
|
| + AXObject* parent;
|
| + for (parent = ParentObject(); parent && parent->AccessibilityIsIgnored();
|
| + parent = parent->ParentObject()) {
|
| + }
|
| +
|
| + return parent;
|
| +}
|
| +
|
| +void AXObject::UpdateChildrenIfNecessary() {
|
| + if (!HasChildren())
|
| + AddChildren();
|
| +}
|
| +
|
| +void AXObject::ClearChildren() {
|
| + // Detach all weak pointers from objects to their parents.
|
| + for (const auto& child : children_)
|
| + child->DetachFromParent();
|
| +
|
| + children_.clear();
|
| + have_children_ = false;
|
| +}
|
| +
|
| +Document* AXObject::GetDocument() const {
|
| + FrameView* frame_view = DocumentFrameView();
|
| + if (!frame_view)
|
| + return 0;
|
| +
|
| + return frame_view->GetFrame().GetDocument();
|
| +}
|
| +
|
| +FrameView* AXObject::DocumentFrameView() const {
|
| + const AXObject* object = this;
|
| + while (object && !object->IsAXLayoutObject())
|
| + object = object->ParentObject();
|
| +
|
| + if (!object)
|
| + return 0;
|
| +
|
| + return object->DocumentFrameView();
|
| +}
|
| +
|
| +String AXObject::Language() const {
|
| + const AtomicString& lang = GetAttribute(langAttr);
|
| + if (!lang.IsEmpty())
|
| + return lang;
|
| +
|
| + AXObject* parent = ParentObject();
|
| +
|
| + // As a last resort, fall back to the content language specified in the meta
|
| + // tag.
|
| + if (!parent) {
|
| + Document* doc = GetDocument();
|
| + if (doc)
|
| + return doc->ContentLanguage();
|
| + return g_null_atom;
|
| + }
|
| +
|
| + return parent->Language();
|
| +}
|
| +
|
| +bool AXObject::HasAttribute(const QualifiedName& attribute) const {
|
| + Node* element_node = GetNode();
|
| + if (!element_node)
|
| + return false;
|
| +
|
| + if (!element_node->IsElementNode())
|
| + return false;
|
| +
|
| + Element* element = ToElement(element_node);
|
| + return element->FastHasAttribute(attribute);
|
| +}
|
| +
|
| +const AtomicString& AXObject::GetAttribute(
|
| + const QualifiedName& attribute) const {
|
| + Node* element_node = GetNode();
|
| + if (!element_node)
|
| + return g_null_atom;
|
| +
|
| + if (!element_node->IsElementNode())
|
| + return g_null_atom;
|
| +
|
| + Element* element = ToElement(element_node);
|
| + return element->FastGetAttribute(attribute);
|
| +}
|
| +
|
| +//
|
| +// Scrollable containers.
|
| +//
|
| +
|
| +bool AXObject::IsScrollableContainer() const {
|
| + return !!GetScrollableAreaIfScrollable();
|
| +}
|
| +
|
| +IntPoint AXObject::GetScrollOffset() const {
|
| + ScrollableArea* area = GetScrollableAreaIfScrollable();
|
| + if (!area)
|
| + return IntPoint();
|
| +
|
| + return IntPoint(area->ScrollOffsetInt().Width(),
|
| + area->ScrollOffsetInt().Height());
|
| +}
|
| +
|
| +IntPoint AXObject::MinimumScrollOffset() const {
|
| + ScrollableArea* area = GetScrollableAreaIfScrollable();
|
| + if (!area)
|
| + return IntPoint();
|
| +
|
| + return IntPoint(area->MinimumScrollOffsetInt().Width(),
|
| + area->MinimumScrollOffsetInt().Height());
|
| +}
|
| +
|
| +IntPoint AXObject::MaximumScrollOffset() const {
|
| + ScrollableArea* area = GetScrollableAreaIfScrollable();
|
| + if (!area)
|
| + return IntPoint();
|
| +
|
| + return IntPoint(area->MaximumScrollOffsetInt().Width(),
|
| + area->MaximumScrollOffsetInt().Height());
|
| +}
|
| +
|
| +void AXObject::SetScrollOffset(const IntPoint& offset) const {
|
| + ScrollableArea* area = GetScrollableAreaIfScrollable();
|
| + if (!area)
|
| + return;
|
| +
|
| + // TODO(bokan): This should potentially be a UserScroll.
|
| + area->SetScrollOffset(ScrollOffset(offset.X(), offset.Y()),
|
| + kProgrammaticScroll);
|
| +}
|
| +
|
| +void AXObject::GetRelativeBounds(AXObject** out_container,
|
| + FloatRect& out_bounds_in_container,
|
| + SkMatrix44& out_container_transform) const {
|
| + *out_container = nullptr;
|
| + out_bounds_in_container = FloatRect();
|
| + out_container_transform.setIdentity();
|
| +
|
| + // First check if it has explicit bounds, for example if this element is tied
|
| + // to a canvas path. When explicit coordinates are provided, the ID of the
|
| + // explicit container element that the coordinates are relative to must be
|
| + // provided too.
|
| + if (!explicit_element_rect_.IsEmpty()) {
|
| + *out_container = AxObjectCache().ObjectFromAXID(explicit_container_id_);
|
| + if (*out_container) {
|
| + out_bounds_in_container = FloatRect(explicit_element_rect_);
|
| + return;
|
| + }
|
| + }
|
| +
|
| + LayoutObject* layout_object = LayoutObjectForRelativeBounds();
|
| + if (!layout_object)
|
| + return;
|
| +
|
| + if (IsWebArea()) {
|
| + if (layout_object->GetFrame()->View())
|
| + out_bounds_in_container.SetSize(
|
| + FloatSize(layout_object->GetFrame()->View()->ContentsSize()));
|
| + return;
|
| + }
|
| +
|
| + // First compute the container. The container must be an ancestor in the
|
| + // accessibility tree, and its LayoutObject must be an ancestor in the layout
|
| + // tree. Get the first such ancestor that's either scrollable or has a paint
|
| + // layer.
|
| + AXObject* container = ParentObjectUnignored();
|
| + LayoutObject* container_layout_object = nullptr;
|
| + while (container) {
|
| + container_layout_object = container->GetLayoutObject();
|
| + if (container_layout_object &&
|
| + container_layout_object->IsBoxModelObject() &&
|
| + layout_object->IsDescendantOf(container_layout_object)) {
|
| + if (container->IsScrollableContainer() ||
|
| + container_layout_object->HasLayer())
|
| + break;
|
| + }
|
| +
|
| + container = container->ParentObjectUnignored();
|
| + }
|
| +
|
| + if (!container)
|
| + return;
|
| + *out_container = container;
|
| + out_bounds_in_container =
|
| + layout_object->LocalBoundingBoxRectForAccessibility();
|
| +
|
| + // If the container has a scroll offset, subtract that out because we want our
|
| + // bounds to be relative to the *unscrolled* position of the container object.
|
| + ScrollableArea* scrollable_area = container->GetScrollableAreaIfScrollable();
|
| + if (scrollable_area && !container->IsWebArea()) {
|
| + ScrollOffset scroll_offset = scrollable_area->GetScrollOffset();
|
| + out_bounds_in_container.Move(scroll_offset);
|
| + }
|
| +
|
| + // Compute the transform between the container's coordinate space and this
|
| + // object. If the transform is just a simple translation, apply that to the
|
| + // bounding box, but if it's a non-trivial transformation like a rotation,
|
| + // scaling, etc. then return the full matrix instead.
|
| + TransformationMatrix transform = layout_object->LocalToAncestorTransform(
|
| + ToLayoutBoxModelObject(container_layout_object));
|
| + if (transform.IsIdentityOr2DTranslation()) {
|
| + out_bounds_in_container.Move(transform.To2DTranslation());
|
| + } else {
|
| + out_container_transform = TransformationMatrix::ToSkMatrix44(transform);
|
| + }
|
| +}
|
| +
|
| +LayoutRect AXObject::GetBoundsInFrameCoordinates() const {
|
| + AXObject* container = nullptr;
|
| + FloatRect bounds;
|
| + SkMatrix44 transform;
|
| + GetRelativeBounds(&container, bounds, transform);
|
| + FloatRect computed_bounds(0, 0, bounds.Width(), bounds.Height());
|
| + while (container && container != this) {
|
| + computed_bounds.Move(bounds.X(), bounds.Y());
|
| + if (!container->IsWebArea()) {
|
| + computed_bounds.Move(-container->GetScrollOffset().X(),
|
| + -container->GetScrollOffset().Y());
|
| + }
|
| + if (!transform.isIdentity()) {
|
| + TransformationMatrix transformation_matrix(transform);
|
| + transformation_matrix.MapRect(computed_bounds);
|
| + }
|
| + container->GetRelativeBounds(&container, bounds, transform);
|
| + }
|
| + return LayoutRect(computed_bounds);
|
| +}
|
| +
|
| +//
|
| +// Modify or take an action on an object.
|
| +//
|
| +
|
| +bool AXObject::Press() {
|
| + Document* document = GetDocument();
|
| + if (!document)
|
| + return false;
|
| +
|
| + UserGestureIndicator gesture_indicator(DocumentUserGestureToken::Create(
|
| + document, UserGestureToken::kNewGesture));
|
| + Element* action_elem = ActionElement();
|
| + if (action_elem) {
|
| + action_elem->AccessKeyAction(true);
|
| + return true;
|
| + }
|
| +
|
| + if (CanSetFocusAttribute()) {
|
| + SetFocused(true);
|
| + return true;
|
| + }
|
| +
|
| + return false;
|
| +}
|
| +
|
| +void AXObject::ScrollToMakeVisible() const {
|
| + IntRect object_rect = PixelSnappedIntRect(GetBoundsInFrameCoordinates());
|
| + object_rect.SetLocation(IntPoint());
|
| + ScrollToMakeVisibleWithSubFocus(object_rect);
|
| +}
|
| +
|
| +// This is a 1-dimensional scroll offset helper function that's applied
|
| +// separately in the horizontal and vertical directions, because the
|
| +// logic is the same. The goal is to compute the best scroll offset
|
| +// in order to make an object visible within a viewport.
|
| +//
|
| +// If the object is already fully visible, returns the same scroll
|
| +// offset.
|
| +//
|
| +// In case the whole object cannot fit, you can specify a
|
| +// subfocus - a smaller region within the object that should
|
| +// be prioritized. If the whole object can fit, the subfocus is
|
| +// ignored.
|
| +//
|
| +// If possible, the object and subfocus are centered within the
|
| +// viewport.
|
| +//
|
| +// Example 1: the object is already visible, so nothing happens.
|
| +// +----------Viewport---------+
|
| +// +---Object---+
|
| +// +--SubFocus--+
|
| +//
|
| +// Example 2: the object is not fully visible, so it's centered
|
| +// within the viewport.
|
| +// Before:
|
| +// +----------Viewport---------+
|
| +// +---Object---+
|
| +// +--SubFocus--+
|
| +//
|
| +// After:
|
| +// +----------Viewport---------+
|
| +// +---Object---+
|
| +// +--SubFocus--+
|
| +//
|
| +// Example 3: the object is larger than the viewport, so the
|
| +// viewport moves to show as much of the object as possible,
|
| +// while also trying to center the subfocus.
|
| +// Before:
|
| +// +----------Viewport---------+
|
| +// +---------------Object--------------+
|
| +// +-SubFocus-+
|
| +//
|
| +// After:
|
| +// +----------Viewport---------+
|
| +// +---------------Object--------------+
|
| +// +-SubFocus-+
|
| +//
|
| +// When constraints cannot be fully satisfied, the min
|
| +// (left/top) position takes precedence over the max (right/bottom).
|
| +//
|
| +// Note that the return value represents the ideal new scroll offset.
|
| +// This may be out of range - the calling function should clip this
|
| +// to the available range.
|
| +static int ComputeBestScrollOffset(int current_scroll_offset,
|
| + int subfocus_min,
|
| + int subfocus_max,
|
| + int object_min,
|
| + int object_max,
|
| + int viewport_min,
|
| + int viewport_max) {
|
| + int viewport_size = viewport_max - viewport_min;
|
| +
|
| + // If the object size is larger than the viewport size, consider
|
| + // only a portion that's as large as the viewport, centering on
|
| + // the subfocus as much as possible.
|
| + if (object_max - object_min > viewport_size) {
|
| + // Since it's impossible to fit the whole object in the
|
| + // viewport, exit now if the subfocus is already within the viewport.
|
| + if (subfocus_min - current_scroll_offset >= viewport_min &&
|
| + subfocus_max - current_scroll_offset <= viewport_max)
|
| + return current_scroll_offset;
|
| +
|
| + // Subfocus must be within focus.
|
| + subfocus_min = std::max(subfocus_min, object_min);
|
| + subfocus_max = std::min(subfocus_max, object_max);
|
| +
|
| + // Subfocus must be no larger than the viewport size; favor top/left.
|
| + if (subfocus_max - subfocus_min > viewport_size)
|
| + subfocus_max = subfocus_min + viewport_size;
|
| +
|
| + // Compute the size of an object centered on the subfocus, the size of the
|
| + // viewport.
|
| + int centered_object_min = (subfocus_min + subfocus_max - viewport_size) / 2;
|
| + int centered_object_max = centered_object_min + viewport_size;
|
| +
|
| + object_min = std::max(object_min, centered_object_min);
|
| + object_max = std::min(object_max, centered_object_max);
|
| + }
|
| +
|
| + // Exit now if the focus is already within the viewport.
|
| + if (object_min - current_scroll_offset >= viewport_min &&
|
| + object_max - current_scroll_offset <= viewport_max)
|
| + return current_scroll_offset;
|
| +
|
| + // Center the object in the viewport.
|
| + return (object_min + object_max - viewport_min - viewport_max) / 2;
|
| +}
|
| +
|
| +void AXObject::ScrollToMakeVisibleWithSubFocus(const IntRect& subfocus) const {
|
| + // Search up the parent chain until we find the first one that's scrollable.
|
| + const AXObject* scroll_parent = ParentObject() ? ParentObject() : this;
|
| + ScrollableArea* scrollable_area = 0;
|
| + while (scroll_parent) {
|
| + scrollable_area = scroll_parent->GetScrollableAreaIfScrollable();
|
| + if (scrollable_area)
|
| + break;
|
| + scroll_parent = scroll_parent->ParentObject();
|
| + }
|
| + if (!scroll_parent || !scrollable_area)
|
| + return;
|
| +
|
| + IntRect object_rect = PixelSnappedIntRect(GetBoundsInFrameCoordinates());
|
| + IntSize scroll_offset = scrollable_area->ScrollOffsetInt();
|
| + IntRect scroll_visible_rect = scrollable_area->VisibleContentRect();
|
| +
|
| + // Convert the object rect into local coordinates.
|
| + if (!scroll_parent->IsWebArea()) {
|
| + object_rect.MoveBy(IntPoint(scroll_offset));
|
| + object_rect.MoveBy(
|
| + -PixelSnappedIntRect(scroll_parent->GetBoundsInFrameCoordinates())
|
| + .Location());
|
| + }
|
| +
|
| + int desired_x = ComputeBestScrollOffset(
|
| + scroll_offset.Width(), object_rect.X() + subfocus.X(),
|
| + object_rect.X() + subfocus.MaxX(), object_rect.X(), object_rect.MaxX(), 0,
|
| + scroll_visible_rect.Width());
|
| + int desired_y = ComputeBestScrollOffset(
|
| + scroll_offset.Height(), object_rect.Y() + subfocus.Y(),
|
| + object_rect.Y() + subfocus.MaxY(), object_rect.Y(), object_rect.MaxY(), 0,
|
| + scroll_visible_rect.Height());
|
| +
|
| + scroll_parent->SetScrollOffset(IntPoint(desired_x, desired_y));
|
| +
|
| + // Convert the subfocus into the coordinates of the scroll parent.
|
| + IntRect new_subfocus = subfocus;
|
| + IntRect new_element_rect = PixelSnappedIntRect(GetBoundsInFrameCoordinates());
|
| + IntRect scroll_parent_rect =
|
| + PixelSnappedIntRect(scroll_parent->GetBoundsInFrameCoordinates());
|
| + new_subfocus.Move(new_element_rect.X(), new_element_rect.Y());
|
| + new_subfocus.Move(-scroll_parent_rect.X(), -scroll_parent_rect.Y());
|
| +
|
| + if (scroll_parent->ParentObject()) {
|
| + // Recursively make sure the scroll parent itself is visible.
|
| + scroll_parent->ScrollToMakeVisibleWithSubFocus(new_subfocus);
|
| + } else {
|
| + // To minimize the number of notifications, only fire one on the topmost
|
| + // object that has been scrolled.
|
| + AxObjectCache().PostNotification(const_cast<AXObject*>(this),
|
| + AXObjectCacheImpl::kAXLocationChanged);
|
| + }
|
| +}
|
| +
|
| +void AXObject::ScrollToGlobalPoint(const IntPoint& global_point) const {
|
| + // Search up the parent chain and create a vector of all scrollable parent
|
| + // objects and ending with this object itself.
|
| + HeapVector<Member<const AXObject>> objects;
|
| + AXObject* parent_object;
|
| + for (parent_object = this->ParentObject(); parent_object;
|
| + parent_object = parent_object->ParentObject()) {
|
| + if (parent_object->GetScrollableAreaIfScrollable())
|
| + objects.push_front(parent_object);
|
| + }
|
| + objects.push_back(this);
|
| +
|
| + // Start with the outermost scrollable (the main window) and try to scroll the
|
| + // next innermost object to the given point.
|
| + int offset_x = 0, offset_y = 0;
|
| + IntPoint point = global_point;
|
| + size_t levels = objects.size() - 1;
|
| + for (size_t i = 0; i < levels; i++) {
|
| + const AXObject* outer = objects[i];
|
| + const AXObject* inner = objects[i + 1];
|
| + ScrollableArea* scrollable_area = outer->GetScrollableAreaIfScrollable();
|
| +
|
| + IntRect inner_rect =
|
| + inner->IsWebArea()
|
| + ? PixelSnappedIntRect(
|
| + inner->ParentObject()->GetBoundsInFrameCoordinates())
|
| + : PixelSnappedIntRect(inner->GetBoundsInFrameCoordinates());
|
| + IntRect object_rect = inner_rect;
|
| + IntSize scroll_offset = scrollable_area->ScrollOffsetInt();
|
| +
|
| + // Convert the object rect into local coordinates.
|
| + object_rect.Move(offset_x, offset_y);
|
| + if (!outer->IsWebArea())
|
| + object_rect.Move(scroll_offset.Width(), scroll_offset.Height());
|
| +
|
| + int desired_x = ComputeBestScrollOffset(
|
| + 0, object_rect.X(), object_rect.MaxX(), object_rect.X(),
|
| + object_rect.MaxX(), point.X(), point.X());
|
| + int desired_y = ComputeBestScrollOffset(
|
| + 0, object_rect.Y(), object_rect.MaxY(), object_rect.Y(),
|
| + object_rect.MaxY(), point.Y(), point.Y());
|
| + outer->SetScrollOffset(IntPoint(desired_x, desired_y));
|
| +
|
| + if (outer->IsWebArea() && !inner->IsWebArea()) {
|
| + // If outer object we just scrolled is a web area (frame) but the inner
|
| + // object is not, keep track of the coordinate transformation to apply to
|
| + // future nested calculations.
|
| + scroll_offset = scrollable_area->ScrollOffsetInt();
|
| + offset_x -= (scroll_offset.Width() + point.X());
|
| + offset_y -= (scroll_offset.Height() + point.Y());
|
| + point.Move(scroll_offset.Width() - inner_rect.Width(),
|
| + scroll_offset.Height() - inner_rect.Y());
|
| + } else if (inner->IsWebArea()) {
|
| + // Otherwise, if the inner object is a web area, reset the coordinate
|
| + // transformation.
|
| + offset_x = 0;
|
| + offset_y = 0;
|
| + }
|
| + }
|
| +
|
| + // To minimize the number of notifications, only fire one on the topmost
|
| + // object that has been scrolled.
|
| + DCHECK(objects[0]);
|
| + // TODO(nektar): Switch to postNotification(objects[0] and remove |getNode|.
|
| + AxObjectCache().PostNotification(objects[0]->GetNode(),
|
| + AXObjectCacheImpl::kAXLocationChanged);
|
| +}
|
| +
|
| +void AXObject::SetSequentialFocusNavigationStartingPoint() {
|
| + // Call it on the nearest ancestor that overrides this with a specific
|
| + // implementation.
|
| + if (ParentObject())
|
| + ParentObject()->SetSequentialFocusNavigationStartingPoint();
|
| +}
|
| +
|
| +void AXObject::NotifyIfIgnoredValueChanged() {
|
| + bool is_ignored = AccessibilityIsIgnored();
|
| + if (LastKnownIsIgnoredValue() != is_ignored) {
|
| + AxObjectCache().ChildrenChanged(ParentObject());
|
| + SetLastKnownIsIgnoredValue(is_ignored);
|
| + }
|
| +}
|
| +
|
| +void AXObject::SelectionChanged() {
|
| + if (AXObject* parent = ParentObjectIfExists())
|
| + parent->SelectionChanged();
|
| +}
|
| +
|
| +int AXObject::LineForPosition(const VisiblePosition& position) const {
|
| + if (position.IsNull() || !GetNode())
|
| + return -1;
|
| +
|
| + // If the position is not in the same editable region as this AX object,
|
| + // return -1.
|
| + Node* container_node = position.DeepEquivalent().ComputeContainerNode();
|
| + if (!container_node->IsShadowIncludingInclusiveAncestorOf(GetNode()) &&
|
| + !GetNode()->IsShadowIncludingInclusiveAncestorOf(container_node))
|
| + return -1;
|
| +
|
| + int line_count = -1;
|
| + VisiblePosition current_position = position;
|
| + VisiblePosition previous_position;
|
| +
|
| + // Move up until we get to the top.
|
| + // FIXME: This only takes us to the top of the rootEditableElement, not the
|
| + // top of the top document.
|
| + do {
|
| + previous_position = current_position;
|
| + current_position = PreviousLinePosition(current_position, LayoutUnit(),
|
| + kHasEditableAXRole);
|
| + ++line_count;
|
| + } while (current_position.IsNotNull() &&
|
| + !InSameLine(current_position, previous_position));
|
| +
|
| + return line_count;
|
| +}
|
| +
|
| +bool AXObject::IsARIAControl(AccessibilityRole aria_role) {
|
| + return IsARIAInput(aria_role) || aria_role == kButtonRole ||
|
| + aria_role == kComboBoxRole || aria_role == kSliderRole;
|
| +}
|
| +
|
| +bool AXObject::IsARIAInput(AccessibilityRole aria_role) {
|
| + return aria_role == kRadioButtonRole || aria_role == kCheckBoxRole ||
|
| + aria_role == kTextFieldRole || aria_role == kSwitchRole ||
|
| + aria_role == kSearchBoxRole;
|
| +}
|
| +
|
| +AccessibilityRole AXObject::AriaRoleToWebCoreRole(const String& value) {
|
| + DCHECK(!value.IsEmpty());
|
| +
|
| + static const ARIARoleMap* role_map = CreateARIARoleMap();
|
| +
|
| + Vector<String> role_vector;
|
| + value.Split(' ', role_vector);
|
| + AccessibilityRole role = kUnknownRole;
|
| + for (const auto& child : role_vector) {
|
| + role = role_map->at(child);
|
| + if (role)
|
| + return role;
|
| + }
|
| +
|
| + return role;
|
| +}
|
| +
|
| +bool AXObject::IsInsideFocusableElementOrARIAWidget(const Node& node) {
|
| + const Node* cur_node = &node;
|
| + do {
|
| + if (cur_node->IsElementNode()) {
|
| + const Element* element = ToElement(cur_node);
|
| + if (element->IsFocusable())
|
| + return true;
|
| + String role = element->getAttribute("role");
|
| + if (!role.IsEmpty() && AXObject::IncludesARIAWidgetRole(role))
|
| + return true;
|
| + if (HasInteractiveARIAAttribute(*element))
|
| + return true;
|
| + }
|
| + cur_node = cur_node->parentNode();
|
| + } while (cur_node && !isHTMLBodyElement(node));
|
| + return false;
|
| +}
|
| +
|
| +bool AXObject::HasInteractiveARIAAttribute(const Element& element) {
|
| + for (size_t i = 0; i < WTF_ARRAY_LENGTH(g_aria_interactive_widget_attributes);
|
| + ++i) {
|
| + const char* attribute = g_aria_interactive_widget_attributes[i];
|
| + if (element.hasAttribute(attribute)) {
|
| + return true;
|
| + }
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +bool AXObject::IncludesARIAWidgetRole(const String& role) {
|
| + static const HashSet<String, CaseFoldingHash>* role_set =
|
| + CreateARIARoleWidgetSet();
|
| +
|
| + Vector<String> role_vector;
|
| + role.Split(' ', role_vector);
|
| + for (const auto& child : role_vector) {
|
| + if (role_set->Contains(child))
|
| + return true;
|
| + }
|
| + return false;
|
| +}
|
| +
|
| +bool AXObject::NameFromContents() const {
|
| + // ARIA 1.1, section 5.2.7.5.
|
| + switch (RoleValue()) {
|
| + case kAnchorRole:
|
| + case kButtonRole:
|
| + case kCellRole:
|
| + case kCheckBoxRole:
|
| + case kColumnHeaderRole:
|
| + case kDirectoryRole:
|
| + case kDisclosureTriangleRole:
|
| + case kHeadingRole:
|
| + case kLineBreakRole:
|
| + case kLinkRole:
|
| + case kListBoxOptionRole:
|
| + case kListItemRole:
|
| + case kMenuItemRole:
|
| + case kMenuItemCheckBoxRole:
|
| + case kMenuItemRadioRole:
|
| + case kMenuListOptionRole:
|
| + case kPopUpButtonRole:
|
| + case kRadioButtonRole:
|
| + case kRowHeaderRole:
|
| + case kStaticTextRole:
|
| + case kStatusRole:
|
| + case kSwitchRole:
|
| + case kTabRole:
|
| + case kToggleButtonRole:
|
| + case kTreeItemRole:
|
| + case kUserInterfaceTooltipRole:
|
| + return true;
|
| + case kRowRole: {
|
| + // Spec says we should always expose the name on rows,
|
| + // but for performance reasons we only do it
|
| + // if the row might receive focus
|
| + if (AncestorExposesActiveDescendant()) {
|
| + return true;
|
| + }
|
| + const Node* node = this->GetNode();
|
| + return node && node->IsElementNode() && ToElement(node)->IsFocusable();
|
| + }
|
| + default:
|
| + return false;
|
| + }
|
| +}
|
| +
|
| +AccessibilityRole AXObject::ButtonRoleType() const {
|
| + // If aria-pressed is present, then it should be exposed as a toggle button.
|
| + // http://www.w3.org/TR/wai-aria/states_and_properties#aria-pressed
|
| + if (AriaPressedIsPresent())
|
| + return kToggleButtonRole;
|
| + if (AriaHasPopup())
|
| + return kPopUpButtonRole;
|
| + // We don't contemplate RadioButtonRole, as it depends on the input
|
| + // type.
|
| +
|
| + return kButtonRole;
|
| +}
|
| +
|
| +const AtomicString& AXObject::RoleName(AccessibilityRole role) {
|
| + static const Vector<AtomicString>* role_name_vector = CreateRoleNameVector();
|
| +
|
| + return role_name_vector->at(role);
|
| +}
|
| +
|
| +const AtomicString& AXObject::InternalRoleName(AccessibilityRole role) {
|
| + static const Vector<AtomicString>* internal_role_name_vector =
|
| + CreateInternalRoleNameVector();
|
| +
|
| + return internal_role_name_vector->at(role);
|
| +}
|
| +
|
| +DEFINE_TRACE(AXObject) {
|
| + visitor->Trace(children_);
|
| + visitor->Trace(parent_);
|
| + visitor->Trace(cached_live_region_root_);
|
| + visitor->Trace(ax_object_cache_);
|
| +}
|
| +
|
| +} // namespace blink
|
|
|