Index: ui/accessibility/platform/ax_platform_node_win.cc |
diff --git a/ui/accessibility/platform/ax_platform_node_win.cc b/ui/accessibility/platform/ax_platform_node_win.cc |
index ca9ef23a165e5e247c421f5fa581a03b3787fa71..e7088f0dbbf419cbf213be47c2c856bbf4e9e9a6 100644 |
--- a/ui/accessibility/platform/ax_platform_node_win.cc |
+++ b/ui/accessibility/platform/ax_platform_node_win.cc |
@@ -204,6 +204,54 @@ AXPlatformNodeWin::AXPlatformNodeWin() { |
AXPlatformNodeWin::~AXPlatformNodeWin() { |
} |
+// Static |
+void AXPlatformNodeWin::SanitizeStringAttributeForIA2( |
+ const base::string16& input, |
+ base::string16* output) { |
+ DCHECK(output); |
+ // According to the IA2 Spec, these characters need to be escaped with a |
+ // backslash: backslash, colon, comma, equals and semicolon. |
+ // Note that backslash must be replaced first. |
+ base::ReplaceChars(input, L"\\", L"\\\\", output); |
+ base::ReplaceChars(*output, L":", L"\\:", output); |
+ base::ReplaceChars(*output, L",", L"\\,", output); |
+ base::ReplaceChars(*output, L"=", L"\\=", output); |
+ base::ReplaceChars(*output, L";", L"\\;", output); |
+} |
+ |
+void AXPlatformNodeWin::StringAttributeToIA2( |
+ std::vector<base::string16>& attributes, |
+ ui::AXStringAttribute attribute, |
+ const char* ia2_attr) { |
+ base::string16 value; |
+ if (GetString16Attribute(attribute, &value)) { |
+ SanitizeStringAttributeForIA2(value, &value); |
+ attributes.push_back(base::ASCIIToUTF16(ia2_attr) + L":" + value); |
+ } |
+} |
+ |
+void AXPlatformNodeWin::BoolAttributeToIA2( |
+ std::vector<base::string16>& attributes, |
+ ui::AXBoolAttribute attribute, |
+ const char* ia2_attr) { |
+ bool value; |
+ if (GetBoolAttribute(attribute, &value)) { |
+ attributes.push_back((base::ASCIIToUTF16(ia2_attr) + L":") + |
+ (value ? L"true" : L"false")); |
+ } |
+} |
+ |
+void AXPlatformNodeWin::IntAttributeToIA2( |
+ std::vector<base::string16>& attributes, |
+ ui::AXIntAttribute attribute, |
+ const char* ia2_attr) { |
+ int value; |
+ if (GetIntAttribute(attribute, &value)) { |
+ attributes.push_back(base::ASCIIToUTF16(ia2_attr) + L":" + |
+ base::IntToString16(value)); |
+ } |
+} |
+ |
// |
// AXPlatformNodeBase implementation. |
// |
@@ -798,7 +846,7 @@ STDMETHODIMP AXPlatformNodeWin::put_accName( |
STDMETHODIMP AXPlatformNodeWin::role(LONG* role) { |
COM_OBJECT_VALIDATE_1_ARG(role); |
- *role = IA2Role(); |
+ *role = ComputeIA2Role(); |
// If we didn't explicitly set the IAccessible2 role, make it the same |
// as the MSAA role. |
if (!*role) |
@@ -808,7 +856,7 @@ STDMETHODIMP AXPlatformNodeWin::role(LONG* role) { |
STDMETHODIMP AXPlatformNodeWin::get_states(AccessibleStates* states) { |
COM_OBJECT_VALIDATE_1_ARG(states); |
- *states = IA2State(); |
+ *states = ComputeIA2State(); |
return S_OK; |
} |
@@ -2220,7 +2268,7 @@ bool AXPlatformNodeWin::IsWebAreaForPresentationalIframe() { |
return parent->GetData().role == ui::AX_ROLE_IFRAME_PRESENTATIONAL; |
} |
-int32_t AXPlatformNodeWin::IA2State() { |
+int32_t AXPlatformNodeWin::ComputeIA2State() { |
const AXNodeData& data = GetData(); |
int32_t ia2_state = IA2_STATE_OPAQUE; |
@@ -2288,9 +2336,9 @@ int32_t AXPlatformNodeWin::IA2State() { |
return ia2_state; |
} |
-// IA2Role() only returns a role if the MSAA role doesn't suffice, |
+// ComputeIA2Role() only returns a role if the MSAA role doesn't suffice, |
// otherwise this method returns 0. See AXPlatformNodeWin::role(). |
-int32_t AXPlatformNodeWin::IA2Role() { |
+int32_t AXPlatformNodeWin::ComputeIA2Role() { |
// If this is a web area for a presentational iframe, give it a role of |
// something other than DOCUMENT so that the fact that it's a separate doc |
// is not exposed to AT. |
@@ -2426,6 +2474,215 @@ int32_t AXPlatformNodeWin::IA2Role() { |
return ia2_role; |
} |
+std::vector<base::string16> AXPlatformNodeWin::ComputeIA2Attributes() { |
+ std::vector<base::string16> result; |
+ // Expose some HTLM and ARIA attributes in the IAccessible2 attributes string. |
+ // "display", "tag", and "xml-roles" have somewhat unusual names for |
+ // historical reasons. Aside from that virtually every ARIA attribute |
+ // is exposed in a really straightforward way, i.e. "aria-foo" is exposed |
+ // as "foo". |
+ StringAttributeToIA2(result, ui::AX_ATTR_DISPLAY, "display"); |
+ StringAttributeToIA2(result, ui::AX_ATTR_HTML_TAG, "tag"); |
+ StringAttributeToIA2(result, ui::AX_ATTR_ROLE, "xml-roles"); |
+ StringAttributeToIA2(result, ui::AX_ATTR_PLACEHOLDER, "placeholder"); |
+ |
+ StringAttributeToIA2(result, ui::AX_ATTR_AUTO_COMPLETE, "autocomplete"); |
+ StringAttributeToIA2(result, ui::AX_ATTR_ROLE_DESCRIPTION, "roledescription"); |
+ StringAttributeToIA2(result, ui::AX_ATTR_KEY_SHORTCUTS, "keyshortcuts"); |
+ |
+ IntAttributeToIA2(result, ui::AX_ATTR_HIERARCHICAL_LEVEL, "level"); |
+ IntAttributeToIA2(result, ui::AX_ATTR_SET_SIZE, "setsize"); |
+ IntAttributeToIA2(result, ui::AX_ATTR_POS_IN_SET, "posinset"); |
+ |
+ if (HasIntAttribute(ui::AX_ATTR_CHECKED_STATE)) |
+ result.push_back(L"checkable:true"); |
+ |
+ // Expose live region attributes. |
+ StringAttributeToIA2(result, ui::AX_ATTR_LIVE_STATUS, "live"); |
+ StringAttributeToIA2(result, ui::AX_ATTR_LIVE_RELEVANT, "relevant"); |
+ BoolAttributeToIA2(result, ui::AX_ATTR_LIVE_ATOMIC, "atomic"); |
+ BoolAttributeToIA2(result, ui::AX_ATTR_LIVE_BUSY, "busy"); |
+ |
+ // Expose container live region attributes. |
+ StringAttributeToIA2(result, ui::AX_ATTR_CONTAINER_LIVE_STATUS, |
+ "container-live"); |
+ StringAttributeToIA2(result, ui::AX_ATTR_CONTAINER_LIVE_RELEVANT, |
+ "container-relevant"); |
+ BoolAttributeToIA2(result, ui::AX_ATTR_CONTAINER_LIVE_ATOMIC, |
+ "container-atomic"); |
+ BoolAttributeToIA2(result, ui::AX_ATTR_CONTAINER_LIVE_BUSY, "container-busy"); |
+ |
+ // Expose the non-standard explicit-name IA2 attribute. |
+ int name_from; |
+ if (GetIntAttribute(ui::AX_ATTR_NAME_FROM, &name_from) && |
+ name_from != ui::AX_NAME_FROM_CONTENTS) { |
+ result.push_back(L"explicit-name:true"); |
+ } |
+ |
+ // Expose the aria-current attribute. |
+ int32_t aria_current_state; |
+ if (GetIntAttribute(ui::AX_ATTR_ARIA_CURRENT_STATE, &aria_current_state)) { |
+ switch (static_cast<ui::AXAriaCurrentState>(aria_current_state)) { |
+ case ui::AX_ARIA_CURRENT_STATE_NONE: |
+ break; |
+ case ui::AX_ARIA_CURRENT_STATE_FALSE: |
+ result.push_back(L"current:false"); |
+ break; |
+ case ui::AX_ARIA_CURRENT_STATE_TRUE: |
+ result.push_back(L"current:true"); |
+ break; |
+ case ui::AX_ARIA_CURRENT_STATE_PAGE: |
+ result.push_back(L"current:page"); |
+ break; |
+ case ui::AX_ARIA_CURRENT_STATE_STEP: |
+ result.push_back(L"current:step"); |
+ break; |
+ case ui::AX_ARIA_CURRENT_STATE_LOCATION: |
+ result.push_back(L"current:location"); |
+ break; |
+ case ui::AX_ARIA_CURRENT_STATE_DATE: |
+ result.push_back(L"current:date"); |
+ break; |
+ case ui::AX_ARIA_CURRENT_STATE_TIME: |
+ result.push_back(L"current:time"); |
+ break; |
+ } |
+ } |
+ |
+ // Expose table cell index. |
+ if (ui::IsCellOrTableHeaderRole(GetData().role)) { |
+ AXPlatformNodeBase* table = FromNativeViewAccessible(GetParent()); |
+ |
+ while (table && !ui::IsTableLikeRole(table->GetData().role)) |
+ table = FromNativeViewAccessible(table->GetParent()); |
+ |
+ if (table) { |
+ const std::vector<int32_t>& unique_cell_ids = |
+ table->GetIntListAttribute(ui::AX_ATTR_UNIQUE_CELL_IDS); |
+ for (size_t i = 0; i < unique_cell_ids.size(); ++i) { |
+ if (unique_cell_ids[i] == GetData().id) { |
+ result.push_back(base::string16(L"table-cell-index:") + |
+ base::IntToString16(static_cast<int>(i))); |
+ } |
+ } |
+ } |
+ } |
+ |
+ // Expose aria-colcount and aria-rowcount in a table, grid or treegrid. |
+ if (ui::IsTableLikeRole(GetData().role)) { |
+ IntAttributeToIA2(result, ui::AX_ATTR_ARIA_COLUMN_COUNT, "colcount"); |
+ IntAttributeToIA2(result, ui::AX_ATTR_ARIA_ROW_COUNT, "rowcount"); |
+ } |
+ |
+ // Expose aria-colindex and aria-rowindex in a cell or row. |
+ if (ui::IsCellOrTableHeaderRole(GetData().role) || |
+ GetData().role == ui::AX_ROLE_ROW) { |
+ if (GetData().role != ui::AX_ROLE_ROW) |
+ IntAttributeToIA2(result, ui::AX_ATTR_ARIA_CELL_COLUMN_INDEX, "colindex"); |
+ IntAttributeToIA2(result, ui::AX_ATTR_ARIA_CELL_ROW_INDEX, "rowindex"); |
+ } |
+ |
+ // Expose row or column header sort direction. |
+ int32_t sort_direction; |
+ if ((MSAARole() == ROLE_SYSTEM_COLUMNHEADER || |
+ MSAARole() == ROLE_SYSTEM_ROWHEADER) && |
+ GetIntAttribute(ui::AX_ATTR_SORT_DIRECTION, &sort_direction)) { |
+ switch (static_cast<ui::AXSortDirection>(sort_direction)) { |
+ case ui::AX_SORT_DIRECTION_NONE: |
+ break; |
+ case ui::AX_SORT_DIRECTION_UNSORTED: |
+ result.push_back(L"sort:none"); |
+ break; |
+ case ui::AX_SORT_DIRECTION_ASCENDING: |
+ result.push_back(L"sort:ascending"); |
+ break; |
+ case ui::AX_SORT_DIRECTION_DESCENDING: |
+ result.push_back(L"sort:descending"); |
+ break; |
+ case ui::AX_SORT_DIRECTION_OTHER: |
+ result.push_back(L"sort:other"); |
+ break; |
+ } |
+ } |
+ |
+ if (ui::IsCellOrTableHeaderRole(GetData().role)) { |
+ // Expose colspan attribute. |
+ base::string16 colspan; |
+ if (GetData().GetHtmlAttribute("aria-colspan", &colspan)) { |
+ SanitizeStringAttributeForIA2(colspan, &colspan); |
+ result.push_back(L"colspan:" + colspan); |
+ } |
+ // Expose rowspan attribute. |
+ base::string16 rowspan; |
+ if (GetData().GetHtmlAttribute("aria-rowspan", &rowspan)) { |
+ SanitizeStringAttributeForIA2(rowspan, &rowspan); |
+ result.push_back(L"rowspan:" + rowspan); |
+ } |
+ } |
+ |
+ // Expose slider value. |
+ if (IsRangeValueSupported()) { |
+ base::string16 value = GetRangeValueText(); |
+ SanitizeStringAttributeForIA2(value, &value); |
+ result.push_back(L"valuetext:" + value); |
+ } |
+ |
+ // Expose dropeffect attribute. |
+ base::string16 drop_effect; |
+ if (GetData().GetHtmlAttribute("aria-dropeffect", &drop_effect)) { |
+ SanitizeStringAttributeForIA2(drop_effect, &drop_effect); |
+ result.push_back(L"dropeffect:" + drop_effect); |
+ } |
+ |
+ // Expose grabbed attribute. |
+ base::string16 grabbed; |
+ if (GetData().GetHtmlAttribute("aria-grabbed", &grabbed)) { |
+ SanitizeStringAttributeForIA2(grabbed, &grabbed); |
+ result.push_back(L"grabbed:" + grabbed); |
+ } |
+ |
+ // Expose class attribute. |
+ base::string16 class_attr; |
+ if (GetData().GetHtmlAttribute("class", &class_attr)) { |
+ SanitizeStringAttributeForIA2(class_attr, &class_attr); |
+ result.push_back(L"class:" + class_attr); |
+ } |
+ |
+ // Expose datetime attribute. |
+ base::string16 datetime; |
+ if (GetData().role == ui::AX_ROLE_TIME && |
+ GetData().GetHtmlAttribute("datetime", &datetime)) { |
+ SanitizeStringAttributeForIA2(datetime, &datetime); |
+ result.push_back(L"datetime:" + datetime); |
+ } |
+ |
+ // Expose id attribute. |
+ base::string16 id; |
+ if (GetData().GetHtmlAttribute("id", &id)) { |
+ SanitizeStringAttributeForIA2(id, &id); |
+ result.push_back(L"id:" + id); |
+ } |
+ |
+ // Expose src attribute. |
+ base::string16 src; |
+ if (GetData().role == ui::AX_ROLE_IMAGE && |
+ GetData().GetHtmlAttribute("src", &src)) { |
+ SanitizeStringAttributeForIA2(src, &src); |
+ result.push_back(L"src:" + src); |
+ } |
+ |
+ // Expose input-text type attribute. |
+ base::string16 type; |
+ base::string16 html_tag = GetString16Attribute(ui::AX_ATTR_HTML_TAG); |
+ if (IsSimpleTextControl() && html_tag == L"input" && |
+ GetData().GetHtmlAttribute("type", &type)) { |
+ SanitizeStringAttributeForIA2(type, &type); |
+ result.push_back(L"text-input-type:" + type); |
+ } |
+ |
+ return result; |
+} |
+ |
bool AXPlatformNodeWin::ShouldNodeHaveReadonlyState( |
const AXNodeData& data) const { |
if (data.GetBoolAttribute(ui::AX_ATTR_ARIA_READONLY)) |