Commit b050223f authored by Dominic Mazzoni's avatar Dominic Mazzoni Committed by Commit Bot

Support some bool properties of virtual AOM nodes.

Unfortunately a ton of refactoring was required to make this
possible, because too much of logic assumed the existence of
an Element, and assumed that we only cared about ARIA on
AXNodeObject subclasses.

I stopped after supporting just 3 properties because this is
a large enough change already.

Bug: 761901
Change-Id: Ie7148f3d4aaf3308d43089260b761f73e56d995b
Reviewed-on: https://chromium-review.googlesource.com/676879Reviewed-by: default avatarKent Tamura <tkent@chromium.org>
Reviewed-by: default avatarAaron Leventhal <aleventhal@chromium.org>
Commit-Queue: Dominic Mazzoni <dmazzoni@chromium.org>
Cr-Commit-Position: refs/heads/master@{#503603}
parent f7448f78
<!DOCTYPE HTML>
<script src="../resources/gc.js"></script>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<!--
Accessibility Object Model
Explainer: https://github.com/WICG/aom/blob/gh-pages/explainer.md
Spec: https://wicg.github.io/aom/spec/
-->
<script>
if (window.internals)
internals.runtimeFlags.accessibilityObjectModelEnabled = true;
</script>
<div id="atomic"></div>
<script>
test(function(t) {
var parent = document.getElementById("atomic");
var accessibleNode = new AccessibleNode();
accessibleNode.role = "region";
parent.accessibleNode.appendChild(accessibleNode);
var axParent = accessibilityController.accessibleElementById("atomic");
var axNode = axParent.childAtIndex(0);
assert_equals(axNode.isAtomic, false);
accessibleNode.atomic = true;
assert_equals(axNode.isAtomic, true);
}, "AccessibleNode.atomic for a virtual AccessibleNode");
</script>
<div id="busy"></div>
<script>
test(function(t) {
var parent = document.getElementById("busy");
var accessibleNode = new AccessibleNode();
accessibleNode.role = "status";
parent.accessibleNode.appendChild(accessibleNode);
var axParent = accessibilityController.accessibleElementById("busy");
var axNode = axParent.childAtIndex(0);
assert_equals(axNode.isBusy, false);
accessibleNode.busy = true;
assert_equals(axNode.isBusy, true);
}, "AccessibleNode.busy for a virtual AccessibleNode");
</script>
<div id="disabled"></div>
<script>
test(function(t) {
var parent = document.getElementById("disabled");
var node = new AccessibleNode();
node.role = "checkbox";
parent.accessibleNode.appendChild(node);
var axParent = accessibilityController.accessibleElementById("disabled");
var axNode = axParent.childAtIndex(0);
assert_equals(axNode.restriction, "none");
node.disabled = true;
assert_equals(axNode.restriction, "disabled");
}, "AccessibleNode.disabled for a virtual AccessibleNode");
</script>
<!--
TODO still:
expanded
hidden
modal
multiline
multiselectable
readOnly
required
selected
-->
......@@ -286,7 +286,7 @@ bool AccessibleNode::GetProperty(Element* element,
template <typename P, typename T>
static T FindPropertyValue(P property,
bool& is_null,
Vector<std::pair<P, T>>& properties,
const Vector<std::pair<P, T>>& properties,
T default_value) {
for (const auto& item : properties) {
if (item.first == property) {
......@@ -298,19 +298,10 @@ static T FindPropertyValue(P property,
return default_value;
}
// static
bool AccessibleNode::GetProperty(Element* element,
AOMBooleanProperty property,
bool& is_null) {
bool AccessibleNode::GetProperty(AOMBooleanProperty property,
bool& is_null) const {
is_null = true;
bool default_value = false;
if (!element || !element->ExistingAccessibleNode())
return default_value;
return FindPropertyValue(
property, is_null, element->ExistingAccessibleNode()->boolean_properties_,
default_value);
return FindPropertyValue(property, is_null, boolean_properties_, false);
}
// static
......@@ -446,9 +437,12 @@ bool AccessibleNode::GetPropertyOrARIAAttribute(Element* element,
if (!element)
return false;
bool result = GetProperty(element, property, is_null);
if (!is_null)
return result;
AccessibleNode* accessible_node = element->ExistingAccessibleNode();
if (accessible_node) {
bool result = accessible_node->GetProperty(property, is_null);
if (!is_null)
return result;
}
// Fall back on the equivalent ARIA attribute.
QualifiedName attribute = GetCorrespondingARIAAttribute(property);
......@@ -514,42 +508,36 @@ int32_t AccessibleNode::GetPropertyOrARIAAttribute(Element* element,
return attr_value.ToInt();
}
// static
void AccessibleNode::GetAllAOMProperties(
Element* element,
AOMPropertyClient* client,
HashSet<QualifiedName>& shadowed_aria_attributes) {
AccessibleNode* accessible_node = element->ExistingAccessibleNode();
if (!accessible_node)
return;
for (auto& item : accessible_node->string_properties_) {
for (auto& item : string_properties_) {
client->AddStringProperty(item.first, item.second);
shadowed_aria_attributes.insert(GetCorrespondingARIAAttribute(item.first));
}
for (auto& item : accessible_node->boolean_properties_) {
for (auto& item : boolean_properties_) {
client->AddBooleanProperty(item.first, item.second);
shadowed_aria_attributes.insert(GetCorrespondingARIAAttribute(item.first));
}
for (auto& item : accessible_node->float_properties_) {
for (auto& item : float_properties_) {
client->AddFloatProperty(item.first, item.second);
shadowed_aria_attributes.insert(GetCorrespondingARIAAttribute(item.first));
}
for (auto& item : accessible_node->int_properties_) {
for (auto& item : int_properties_) {
client->AddIntProperty(item.first, item.second);
shadowed_aria_attributes.insert(GetCorrespondingARIAAttribute(item.first));
}
for (auto& item : accessible_node->uint_properties_) {
for (auto& item : uint_properties_) {
client->AddUIntProperty(item.first, item.second);
shadowed_aria_attributes.insert(GetCorrespondingARIAAttribute(item.first));
}
for (auto& item : accessible_node->relation_properties_) {
for (auto& item : relation_properties_) {
if (!item.second)
continue;
client->AddRelationProperty(item.first, *item.second);
shadowed_aria_attributes.insert(GetCorrespondingARIAAttribute(item.first));
}
for (auto& item : accessible_node->relation_list_properties_) {
for (auto& item : relation_list_properties_) {
if (!item.second)
continue;
client->AddRelationListProperty(item.first, *item.second);
......@@ -568,7 +556,7 @@ void AccessibleNode::setActiveDescendant(AccessibleNode* active_descendant) {
}
bool AccessibleNode::atomic(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kAtomic, is_null);
return GetProperty(AOMBooleanProperty::kAtomic, is_null);
}
void AccessibleNode::setAtomic(bool atomic, bool is_null) {
......@@ -586,7 +574,7 @@ void AccessibleNode::setAutocomplete(const AtomicString& autocomplete) {
}
bool AccessibleNode::busy(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kBusy, is_null);
return GetProperty(AOMBooleanProperty::kBusy, is_null);
}
void AccessibleNode::setBusy(bool busy, bool is_null) {
......@@ -669,7 +657,7 @@ void AccessibleNode::setDetails(AccessibleNode* details) {
}
bool AccessibleNode::disabled(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kDisabled, is_null);
return GetProperty(AOMBooleanProperty::kDisabled, is_null);
}
void AccessibleNode::setDisabled(bool disabled, bool is_null) {
......@@ -687,7 +675,7 @@ void AccessibleNode::setErrorMessage(AccessibleNode* error_message) {
}
bool AccessibleNode::expanded(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kExpanded, is_null);
return GetProperty(AOMBooleanProperty::kExpanded, is_null);
}
void AccessibleNode::setExpanded(bool expanded, bool is_null) {
......@@ -714,7 +702,7 @@ void AccessibleNode::setHasPopUp(const AtomicString& has_popup) {
}
bool AccessibleNode::hidden(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kHidden, is_null);
return GetProperty(AOMBooleanProperty::kHidden, is_null);
}
void AccessibleNode::setHidden(bool hidden, bool is_null) {
......@@ -777,7 +765,7 @@ void AccessibleNode::setLive(const AtomicString& live) {
}
bool AccessibleNode::modal(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kModal, is_null);
return GetProperty(AOMBooleanProperty::kModal, is_null);
}
void AccessibleNode::setModal(bool modal, bool is_null) {
......@@ -786,7 +774,7 @@ void AccessibleNode::setModal(bool modal, bool is_null) {
}
bool AccessibleNode::multiline(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kMultiline, is_null);
return GetProperty(AOMBooleanProperty::kMultiline, is_null);
}
void AccessibleNode::setMultiline(bool multiline, bool is_null) {
......@@ -795,7 +783,7 @@ void AccessibleNode::setMultiline(bool multiline, bool is_null) {
}
bool AccessibleNode::multiselectable(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kMultiselectable, is_null);
return GetProperty(AOMBooleanProperty::kMultiselectable, is_null);
}
void AccessibleNode::setMultiselectable(bool multiselectable, bool is_null) {
......@@ -850,7 +838,7 @@ void AccessibleNode::setPressed(const AtomicString& pressed) {
}
bool AccessibleNode::readOnly(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kReadOnly, is_null);
return GetProperty(AOMBooleanProperty::kReadOnly, is_null);
}
void AccessibleNode::setReadOnly(bool read_only, bool is_null) {
......@@ -868,7 +856,7 @@ void AccessibleNode::setRelevant(const AtomicString& relevant) {
}
bool AccessibleNode::required(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kRequired, is_null);
return GetProperty(AOMBooleanProperty::kRequired, is_null);
}
void AccessibleNode::setRequired(bool required, bool is_null) {
......@@ -922,7 +910,7 @@ void AccessibleNode::setRowSpan(uint32_t row_span, bool is_null) {
}
bool AccessibleNode::selected(bool& is_null) const {
return GetProperty(element_, AOMBooleanProperty::kSelected, is_null);
return GetProperty(AOMBooleanProperty::kSelected, is_null);
}
void AccessibleNode::setSelected(bool selected, bool is_null) {
......
......@@ -126,7 +126,7 @@ class CORE_EXPORT AccessibleNode : public EventTargetWithInlineData {
// explicitly, never AccessibleNodes from DOM Elements.
HeapVector<Member<AccessibleNode>> GetChildren() { return children_; }
// Returns the given string property if the Element has an AccessibleNode.
// Returns the given string property.
const AtomicString& GetProperty(AOMStringProperty) const;
// Returns the given relation property if the Element has an AccessibleNode.
......@@ -139,10 +139,12 @@ class CORE_EXPORT AccessibleNode : public EventTargetWithInlineData {
AOMRelationListProperty,
HeapVector<Member<Element>>&);
// Returns the given boolean property.
bool GetProperty(AOMBooleanProperty, bool& is_null) const;
// Returns the value of the given property if the
// Element has an AccessibleNode. Sets |isNull| if the property and
// attribute are not present.
static bool GetProperty(Element*, AOMBooleanProperty, bool& is_null);
static float GetProperty(Element*, AOMFloatProperty, bool& is_null);
static int32_t GetProperty(Element*, AOMIntProperty, bool& is_null);
static uint32_t GetProperty(Element*, AOMUIntProperty, bool& is_null);
......@@ -192,10 +194,8 @@ class CORE_EXPORT AccessibleNode : public EventTargetWithInlineData {
// with the value of the AOM property if set. Updates
// |shadowed_aria_attributes| to contain a list of the ARIA attributes that
// would be shadowed by these AOM properties.
static void GetAllAOMProperties(
Element*,
AOMPropertyClient*,
HashSet<QualifiedName>& shadowed_aria_attributes);
void GetAllAOMProperties(AOMPropertyClient*,
HashSet<QualifiedName>& shadowed_aria_attributes);
AccessibleNode* activeDescendant() const;
void setActiveDescendant(AccessibleNode*);
......
......@@ -1414,16 +1414,6 @@ const AtomicString& AXLayoutObject::LiveRegionRelevant() const {
return relevant;
}
bool AXLayoutObject::LiveRegionAtomic() const {
bool atomic = false;
if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kAtomic, atomic))
return atomic;
// ARIA roles "alert" and "status" should have an implicit aria-atomic value
// of true.
return RoleValue() == kAlertRole || RoleValue() == kStatusRole;
}
//
// Hit testing.
//
......
......@@ -129,7 +129,6 @@ class MODULES_EXPORT AXLayoutObject : public AXNodeObject {
// ARIA live-region features.
const AtomicString& LiveRegionStatus() const override;
const AtomicString& LiveRegionRelevant() const override;
bool LiveRegionAtomic() const override;
// AX name calc.
String TextAlternative(bool recursive,
......
......@@ -92,8 +92,6 @@ class MODULES_EXPORT AXNodeObject : public AXObject {
bool IsDetached() const override { return !node_; }
bool IsAXNodeObject() const final { return true; }
void GetSparseAXAttributes(AXSparseAttributeClient&) const override;
// Check object role or purpose.
bool IsAnchor() const final;
bool IsControllingVideoElement() const;
......@@ -128,7 +126,6 @@ class MODULES_EXPORT AXNodeObject : public AXObject {
bool IsModal() const final;
bool IsRequired() const final;
bool IsControl() const;
bool CanSupportAriaReadOnly() const;
AXRestriction Restriction() const override;
// Properties of static elements.
......
......@@ -32,6 +32,7 @@
#include "core/InputTypeNames.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/dom/AccessibleNode.h"
#include "core/dom/AccessibleNodeList.h"
#include "core/dom/UserGestureIndicator.h"
#include "core/editing/EditingUtilities.h"
#include "core/editing/VisiblePosition.h"
......@@ -49,6 +50,7 @@
#include "core/layout/LayoutView.h"
#include "core/page/Page.h"
#include "modules/accessibility/AXObjectCacheImpl.h"
#include "modules/accessibility/AXSparseAttributeSetter.h"
#include "platform/text/PlatformLocale.h"
#include "platform/wtf/HashSet.h"
#include "platform/wtf/StdLibExtras.h"
......@@ -485,6 +487,44 @@ bool AXObject::HasAOMPropertyOrARIAAttribute(AOMStringProperty property,
return !result.IsNull();
}
AccessibleNode* AXObject::GetAccessibleNode() const {
Element* element = GetElement();
if (!element)
return nullptr;
return element->ExistingAccessibleNode();
}
void AXObject::GetSparseAXAttributes(
AXSparseAttributeClient& sparse_attribute_client) const {
AXSparseAttributeAOMPropertyClient property_client(*ax_object_cache_,
sparse_attribute_client);
HashSet<QualifiedName> shadowed_aria_attributes;
AccessibleNode* accessible_node = GetAccessibleNode();
if (accessible_node) {
accessible_node->GetAllAOMProperties(&property_client,
shadowed_aria_attributes);
}
Element* element = GetElement();
if (!element)
return;
AXSparseAttributeSetterMap& ax_sparse_attribute_setter_map =
GetSparseAttributeSetterMap();
AttributeCollection attributes = element->AttributesWithoutUpdate();
for (const Attribute& attr : attributes) {
if (shadowed_aria_attributes.Contains(attr.GetName()))
continue;
AXSparseAttributeSetter* setter =
ax_sparse_attribute_setter_map.at(attr.GetName());
if (setter)
setter->Run(*this, sparse_attribute_client, attr.Value());
}
}
bool AXObject::IsARIATextControl() const {
return AriaRoleAttribute() == kTextFieldRole ||
AriaRoleAttribute() == kSearchBoxRole ||
......@@ -1486,6 +1526,34 @@ bool AXObject::IsLiveRegion() const {
return !live_region.IsEmpty() && !EqualIgnoringASCIICase(live_region, "off");
}
AXRestriction AXObject::Restriction() const {
// According to ARIA, all elements of the base markup can be disabled.
// According to CORE-AAM, any focusable descendant of aria-disabled
// ancestor is also disabled.
bool is_disabled;
if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kDisabled,
is_disabled)) {
// Has aria-disabled, overrides native markup determining disabled.
if (is_disabled)
return kDisabled;
} else if (CanSetFocusAttribute() && IsDescendantOfDisabledNode()) {
// No aria-disabled, but other markup says it's disabled.
return kDisabled;
}
// Check aria-readonly if supported by current role.
bool is_read_only;
if (CanSupportAriaReadOnly() &&
HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kReadOnly,
is_read_only)) {
// ARIA overrides other readonly state markup.
return is_read_only ? kReadOnly : kNone;
}
// This is a node that is not readonly and not disabled.
return kNone;
}
AccessibilityRole AXObject::DetermineAccessibilityRole() {
aria_role_ = DetermineAriaRoleAttribute();
return aria_role_;
......@@ -1563,6 +1631,16 @@ AXObject* AXObject::LiveRegionRoot() const {
return cached_live_region_root_;
}
bool AXObject::LiveRegionAtomic() const {
bool atomic = false;
if (HasAOMPropertyOrARIAAttribute(AOMBooleanProperty::kAtomic, atomic))
return atomic;
// ARIA roles "alert" and "status" should have an implicit aria-atomic value
// of true.
return RoleValue() == kAlertRole || RoleValue() == kStatusRole;
}
const AtomicString& AXObject::ContainerLiveRegionStatus() const {
UpdateCachedAttributeValuesIfNeeded();
return cached_live_region_root_ ? cached_live_region_root_->LiveRegionStatus()
......@@ -2346,6 +2424,38 @@ bool AXObject::NameFromContents(bool recursive) const {
return result;
}
bool AXObject::CanSupportAriaReadOnly() const {
switch (RoleValue()) {
case kCellRole:
case kCheckBoxRole:
case kColorWellRole:
case kColumnHeaderRole:
case kComboBoxRole:
case kDateRole:
case kDateTimeRole:
case kGridRole:
case kInputTimeRole:
case kListBoxRole:
case kMenuButtonRole:
case kMenuItemCheckBoxRole:
case kMenuItemRadioRole:
case kPopUpButtonRole:
case kRadioGroupRole:
case kRowHeaderRole:
case kSearchBoxRole:
case kSliderRole:
case kSpinButtonRole:
case kSwitchRole:
case kTextFieldRole:
case kToggleButtonRole:
case kTreeGridRole:
return true;
default:
break;
}
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
......
......@@ -360,7 +360,8 @@ class MODULES_EXPORT AXObject : public GarbageCollectedFinalized<AXObject> {
HeapVector<Member<Element>>& result) const;
bool HasAOMPropertyOrARIAAttribute(AOMRelationListProperty,
HeapVector<Member<Element>>& result) const;
bool HasAOMPropertyOrARIAAttribute(AOMBooleanProperty, bool& result) const;
virtual bool HasAOMPropertyOrARIAAttribute(AOMBooleanProperty,
bool& result) const;
bool AOMPropertyOrARIAAttributeIsTrue(AOMBooleanProperty) const;
bool AOMPropertyOrARIAAttributeIsFalse(AOMBooleanProperty) const;
bool HasAOMPropertyOrARIAAttribute(AOMUIntProperty, uint32_t& result) const;
......@@ -368,10 +369,11 @@ class MODULES_EXPORT AXObject : public GarbageCollectedFinalized<AXObject> {
bool HasAOMPropertyOrARIAAttribute(AOMFloatProperty, float& result) const;
bool HasAOMPropertyOrARIAAttribute(AOMStringProperty,
AtomicString& result) const;
virtual AccessibleNode* GetAccessibleNode() const;
void TokenVectorFromAttribute(Vector<String>&, const QualifiedName&) const;
virtual void GetSparseAXAttributes(AXSparseAttributeClient&) const {}
virtual void GetSparseAXAttributes(AXSparseAttributeClient&) const;
// Determine subclass type.
virtual bool IsAXNodeObject() const { return false; }
......@@ -634,7 +636,7 @@ class MODULES_EXPORT AXObject : public GarbageCollectedFinalized<AXObject> {
virtual bool MaxValueForRange(float* out_value) const { return false; }
virtual bool MinValueForRange(float* out_value) const { return false; }
virtual String StringValue() const { return String(); }
virtual AXRestriction Restriction() const { return kNone; }
virtual AXRestriction Restriction() const;
// ARIA attributes.
virtual AccessibilityRole DetermineAccessibilityRole();
......@@ -675,7 +677,7 @@ class MODULES_EXPORT AXObject : public GarbageCollectedFinalized<AXObject> {
AXObject* LiveRegionRoot() const;
virtual const AtomicString& LiveRegionStatus() const { return g_null_atom; }
virtual const AtomicString& LiveRegionRelevant() const { return g_null_atom; }
virtual bool LiveRegionAtomic() const { return false; }
bool LiveRegionAtomic() const;
const AtomicString& ContainerLiveRegionStatus() const;
const AtomicString& ContainerLiveRegionRelevant() const;
......@@ -878,6 +880,7 @@ class MODULES_EXPORT AXObject : public GarbageCollectedFinalized<AXObject> {
bool CanReceiveAccessibilityFocus() const;
bool NameFromContents(bool recursive) const;
bool CanSupportAriaReadOnly() const;
AccessibilityRole ButtonRoleType() const;
......
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "modules/accessibility/AXSparseAttributeSetter.h"
#include "modules/accessibility/AXObjectCacheImpl.h"
namespace blink {
using namespace HTMLNames;
class BoolAttributeSetter : public AXSparseAttributeSetter {
public:
BoolAttributeSetter(AXBoolAttribute attribute) : attribute_(attribute) {}
private:
AXBoolAttribute attribute_;
void Run(const AXObject& obj,
AXSparseAttributeClient& attribute_map,
const AtomicString& value) {
// ARIA booleans are true if not "false" and not specifically undefined.
bool is_true = !AccessibleNode::IsUndefinedAttrValue(value) &&
!EqualIgnoringASCIICase(value, "false");
if (is_true) // Not necessary to add if false
attribute_map.AddBoolAttribute(attribute_, true);
}
};
class StringAttributeSetter : public AXSparseAttributeSetter {
public:
StringAttributeSetter(AXStringAttribute attribute) : attribute_(attribute) {}
private:
AXStringAttribute attribute_;
void Run(const AXObject& obj,
AXSparseAttributeClient& attribute_map,
const AtomicString& value) {
attribute_map.AddStringAttribute(attribute_, value);
}
};
class ObjectAttributeSetter : public AXSparseAttributeSetter {
public:
ObjectAttributeSetter(AXObjectAttribute attribute) : attribute_(attribute) {}
private:
AXObjectAttribute attribute_;
void Run(const AXObject& obj,
AXSparseAttributeClient& attribute_map,
const AtomicString& value) {
if (value.IsNull() || value.IsEmpty())
return;
Node* node = obj.GetNode();
if (!node || !node->IsElementNode())
return;
Element* target = ToElement(node)->GetTreeScope().getElementById(value);
if (!target)
return;
AXObject* ax_target = obj.AxObjectCache().GetOrCreate(target);
if (ax_target)
attribute_map.AddObjectAttribute(attribute_, *ax_target);
}
};
class ObjectVectorAttributeSetter : public AXSparseAttributeSetter {
public:
ObjectVectorAttributeSetter(AXObjectVectorAttribute attribute)
: attribute_(attribute) {}
private:
AXObjectVectorAttribute attribute_;
void Run(const AXObject& obj,
AXSparseAttributeClient& attribute_map,
const AtomicString& value) {
Node* node = obj.GetNode();
if (!node || !node->IsElementNode())
return;
String attribute_value = value.GetString();
if (attribute_value.IsEmpty())
return;
attribute_value.SimplifyWhiteSpace();
Vector<String> ids;
attribute_value.Split(' ', ids);
if (ids.IsEmpty())
return;
HeapVector<Member<AXObject>> objects;
TreeScope& scope = node->GetTreeScope();
for (const auto& id : ids) {
if (Element* id_element = scope.getElementById(AtomicString(id))) {
AXObject* ax_id_element = obj.AxObjectCache().GetOrCreate(id_element);
if (ax_id_element && !ax_id_element->AccessibilityIsIgnored())
objects.push_back(ax_id_element);
}
}
attribute_map.AddObjectVectorAttribute(attribute_, objects);
}
};
AXSparseAttributeSetterMap& GetSparseAttributeSetterMap() {
// Use a map from attribute name to properties of that attribute.
// That way we only need to iterate over the list of attributes once,
// rather than calling getAttribute() once for each possible obscure
// accessibility attribute.
DEFINE_STATIC_LOCAL(AXSparseAttributeSetterMap,
ax_sparse_attribute_setter_map, ());
if (ax_sparse_attribute_setter_map.IsEmpty()) {
ax_sparse_attribute_setter_map.Set(
aria_activedescendantAttr,
new ObjectAttributeSetter(AXObjectAttribute::kAriaActiveDescendant));
ax_sparse_attribute_setter_map.Set(
aria_controlsAttr, new ObjectVectorAttributeSetter(
AXObjectVectorAttribute::kAriaControls));
ax_sparse_attribute_setter_map.Set(
aria_flowtoAttr,
new ObjectVectorAttributeSetter(AXObjectVectorAttribute::kAriaFlowTo));
ax_sparse_attribute_setter_map.Set(
aria_detailsAttr,
new ObjectAttributeSetter(AXObjectAttribute::kAriaDetails));
ax_sparse_attribute_setter_map.Set(
aria_errormessageAttr,
new ObjectAttributeSetter(AXObjectAttribute::kAriaErrorMessage));
ax_sparse_attribute_setter_map.Set(
aria_keyshortcutsAttr,
new StringAttributeSetter(AXStringAttribute::kAriaKeyShortcuts));
ax_sparse_attribute_setter_map.Set(
aria_roledescriptionAttr,
new StringAttributeSetter(AXStringAttribute::kAriaRoleDescription));
ax_sparse_attribute_setter_map.Set(
aria_busyAttr, new BoolAttributeSetter(AXBoolAttribute::kAriaBusy));
}
return ax_sparse_attribute_setter_map;
}
void AXSparseAttributeAOMPropertyClient::AddStringProperty(
AOMStringProperty property,
const String& value) {
AXStringAttribute attribute;
switch (property) {
case AOMStringProperty::kKeyShortcuts:
attribute = AXStringAttribute::kAriaKeyShortcuts;
break;
case AOMStringProperty::kRoleDescription:
attribute = AXStringAttribute::kAriaRoleDescription;
break;
default:
return;
}
sparse_attribute_client_.AddStringAttribute(attribute, value);
}
void AXSparseAttributeAOMPropertyClient::AddBooleanProperty(
AOMBooleanProperty property,
bool value) {
AXBoolAttribute attribute;
switch (property) {
case AOMBooleanProperty::kBusy:
attribute = AXBoolAttribute::kAriaBusy;
break;
default:
return;
}
sparse_attribute_client_.AddBoolAttribute(attribute, value);
}
void AXSparseAttributeAOMPropertyClient::AddIntProperty(AOMIntProperty property,
int32_t value) {}
void AXSparseAttributeAOMPropertyClient::AddUIntProperty(
AOMUIntProperty property,
uint32_t value) {}
void AXSparseAttributeAOMPropertyClient::AddFloatProperty(
AOMFloatProperty property,
float value) {}
void AXSparseAttributeAOMPropertyClient::AddRelationProperty(
AOMRelationProperty property,
const AccessibleNode& value) {
AXObjectAttribute attribute;
switch (property) {
case AOMRelationProperty::kActiveDescendant:
attribute = AXObjectAttribute::kAriaActiveDescendant;
break;
case AOMRelationProperty::kDetails:
attribute = AXObjectAttribute::kAriaDetails;
break;
case AOMRelationProperty::kErrorMessage:
attribute = AXObjectAttribute::kAriaErrorMessage;
break;
default:
return;
}
Element* target_element = value.element();
AXObject* target_obj = ax_object_cache_->GetOrCreate(target_element);
if (target_element)
sparse_attribute_client_.AddObjectAttribute(attribute, *target_obj);
}
void AXSparseAttributeAOMPropertyClient::AddRelationListProperty(
AOMRelationListProperty property,
const AccessibleNodeList& relations) {
AXObjectVectorAttribute attribute;
switch (property) {
case AOMRelationListProperty::kControls:
attribute = AXObjectVectorAttribute::kAriaControls;
break;
case AOMRelationListProperty::kFlowTo:
attribute = AXObjectVectorAttribute::kAriaFlowTo;
break;
default:
return;
}
HeapVector<Member<AXObject>> objects;
for (size_t i = 0; i < relations.length(); ++i) {
AccessibleNode* accessible_node = relations.item(i);
if (accessible_node) {
Element* element = accessible_node->element();
AXObject* ax_element = ax_object_cache_->GetOrCreate(element);
if (ax_element && !ax_element->AccessibilityIsIgnored())
objects.push_back(ax_element);
}
}
sparse_attribute_client_.AddObjectVectorAttribute(attribute, objects);
}
} // namespace blink
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef AXSparseAttributeSetter_h
#define AXSparseAttributeSetter_h
#include "core/dom/AccessibleNode.h"
#include "core/dom/AccessibleNodeList.h"
#include "modules/accessibility/AXObject.h"
#include "platform/wtf/Allocator.h"
namespace blink {
class AXSparseAttributeSetter {
USING_FAST_MALLOC(AXSparseAttributeSetter);
public:
virtual void Run(const AXObject&,
AXSparseAttributeClient&,
const AtomicString& value) = 0;
};
using AXSparseAttributeSetterMap =
HashMap<QualifiedName, AXSparseAttributeSetter*>;
// A map from attribute name to a AXSparseAttributeSetter that
// calls AXSparseAttributeClient when that attribute's value
// changes.
//
// That way we only need to iterate over the list of attributes once,
// rather than calling getAttribute() once for each possible obscure
// accessibility attribute.
AXSparseAttributeSetterMap& GetSparseAttributeSetterMap();
// An implementation of AOMPropertyClient that calls
// AXSparseAttributeClient for an AOM property.
class AXSparseAttributeAOMPropertyClient : public AOMPropertyClient {
public:
AXSparseAttributeAOMPropertyClient(
AXObjectCacheImpl& ax_object_cache,
AXSparseAttributeClient& sparse_attribute_client)
: ax_object_cache_(ax_object_cache),
sparse_attribute_client_(sparse_attribute_client) {}
void AddStringProperty(AOMStringProperty, const String& value) override;
void AddBooleanProperty(AOMBooleanProperty, bool value) override;
void AddIntProperty(AOMIntProperty, int32_t value) override;
void AddUIntProperty(AOMUIntProperty, uint32_t value) override;
void AddFloatProperty(AOMFloatProperty, float value) override;
void AddRelationProperty(AOMRelationProperty,
const AccessibleNode& value) override;
void AddRelationListProperty(AOMRelationListProperty,
const AccessibleNodeList& relations) override;
private:
Persistent<AXObjectCacheImpl> ax_object_cache_;
AXSparseAttributeClient& sparse_attribute_client_;
};
} // namespace blink
#endif // AXSparseAttributeSetter_h
......@@ -41,6 +41,20 @@ const AtomicString& AXVirtualObject::GetAOMPropertyOrARIAAttribute(
return accessible_node_->GetProperty(property);
}
bool AXVirtualObject::HasAOMPropertyOrARIAAttribute(AOMBooleanProperty property,
bool& result) const {
if (!accessible_node_)
return false;
bool is_null = true;
result = accessible_node_->GetProperty(property, is_null);
return !is_null;
}
AccessibleNode* AXVirtualObject::GetAccessibleNode() const {
return accessible_node_;
}
String AXVirtualObject::TextAlternative(bool recursive,
bool in_aria_labelled_by_traversal,
AXObjectSet& visited,
......
......@@ -26,6 +26,9 @@ class MODULES_EXPORT AXVirtualObject : public AXObject {
void AddChildren() override;
const AtomicString& GetAOMPropertyOrARIAAttribute(
AOMStringProperty) const override;
bool HasAOMPropertyOrARIAAttribute(AOMBooleanProperty,
bool& result) const override;
AccessibleNode* GetAccessibleNode() const override;
String TextAlternative(bool recursive,
bool in_aria_labelled_by_traversal,
AXObjectSet& visited,
......
......@@ -52,6 +52,8 @@ blink_modules_sources("accessibility") {
"AXSVGRoot.h",
"AXSlider.cpp",
"AXSlider.h",
"AXSparseAttributeSetter.cpp",
"AXSparseAttributeSetter.h",
"AXSpinButton.cpp",
"AXSpinButton.h",
"AXTable.cpp",
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment