Chromium Code Reviews| 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..df2e25f3719e6c2b8a66f08cf80b0e1e8052cc63 100644 |
| --- a/ui/accessibility/platform/ax_platform_node_win.cc |
| +++ b/ui/accessibility/platform/ax_platform_node_win.cc |
| @@ -204,6 +204,48 @@ 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(ui::AXStringAttribute attribute, |
| + const char* ia2_attr) { |
| + base::string16 value; |
| + if (GetString16Attribute(attribute, &value)) { |
| + SanitizeStringAttributeForIA2(value, &value); |
| + ia2_attributes_.push_back(base::ASCIIToUTF16(ia2_attr) + L":" + value); |
| + } |
| +} |
| + |
| +void AXPlatformNodeWin::BoolAttributeToIA2(ui::AXBoolAttribute attribute, |
| + const char* ia2_attr) { |
| + bool value; |
| + if (GetBoolAttribute(attribute, &value)) { |
| + ia2_attributes_.push_back((base::ASCIIToUTF16(ia2_attr) + L":") + |
| + (value ? L"true" : L"false")); |
| + } |
| +} |
| + |
| +void AXPlatformNodeWin::IntAttributeToIA2(ui::AXIntAttribute attribute, |
| + const char* ia2_attr) { |
| + int value; |
| + if (GetIntAttribute(attribute, &value)) { |
| + ia2_attributes_.push_back(base::ASCIIToUTF16(ia2_attr) + L":" + |
| + base::IntToString16(value)); |
| + } |
| +} |
| + |
| // |
| // AXPlatformNodeBase implementation. |
| // |
| @@ -2426,6 +2468,215 @@ int32_t AXPlatformNodeWin::IA2Role() { |
| return ia2_role; |
| } |
| +std::vector<base::string16> AXPlatformNodeWin::IA2Attributes() { |
| + // maybe use a cache? |
| + ia2_attributes_.clear(); |
|
dmazzoni
2017/07/17 08:27:18
It doesn't seem to make sense to store ia2_attribu
dougt
2017/07/17 19:28:27
Done.
|
| + |
| + // 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(ui::AX_ATTR_DISPLAY, "display"); |
| + StringAttributeToIA2(ui::AX_ATTR_HTML_TAG, "tag"); |
| + StringAttributeToIA2(ui::AX_ATTR_ROLE, "xml-roles"); |
| + StringAttributeToIA2(ui::AX_ATTR_PLACEHOLDER, "placeholder"); |
| + |
| + StringAttributeToIA2(ui::AX_ATTR_AUTO_COMPLETE, "autocomplete"); |
| + StringAttributeToIA2(ui::AX_ATTR_ROLE_DESCRIPTION, "roledescription"); |
| + StringAttributeToIA2(ui::AX_ATTR_KEY_SHORTCUTS, "keyshortcuts"); |
| + |
| + IntAttributeToIA2(ui::AX_ATTR_HIERARCHICAL_LEVEL, "level"); |
| + IntAttributeToIA2(ui::AX_ATTR_SET_SIZE, "setsize"); |
| + IntAttributeToIA2(ui::AX_ATTR_POS_IN_SET, "posinset"); |
| + |
| + if (HasIntAttribute(ui::AX_ATTR_CHECKED_STATE)) |
| + ia2_attributes_.push_back(L"checkable:true"); |
| + |
| + // Expose live region attributes. |
| + StringAttributeToIA2(ui::AX_ATTR_LIVE_STATUS, "live"); |
| + StringAttributeToIA2(ui::AX_ATTR_LIVE_RELEVANT, "relevant"); |
| + BoolAttributeToIA2(ui::AX_ATTR_LIVE_ATOMIC, "atomic"); |
| + BoolAttributeToIA2(ui::AX_ATTR_LIVE_BUSY, "busy"); |
| + |
| + // Expose container live region attributes. |
| + StringAttributeToIA2(ui::AX_ATTR_CONTAINER_LIVE_STATUS, "container-live"); |
| + StringAttributeToIA2(ui::AX_ATTR_CONTAINER_LIVE_RELEVANT, |
| + "container-relevant"); |
| + BoolAttributeToIA2(ui::AX_ATTR_CONTAINER_LIVE_ATOMIC, "container-atomic"); |
| + BoolAttributeToIA2(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) { |
| + ia2_attributes_.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: |
| + ia2_attributes_.push_back(L"current:false"); |
| + break; |
| + case ui::AX_ARIA_CURRENT_STATE_TRUE: |
| + ia2_attributes_.push_back(L"current:true"); |
| + break; |
| + case ui::AX_ARIA_CURRENT_STATE_PAGE: |
| + ia2_attributes_.push_back(L"current:page"); |
| + break; |
| + case ui::AX_ARIA_CURRENT_STATE_STEP: |
| + ia2_attributes_.push_back(L"current:step"); |
| + break; |
| + case ui::AX_ARIA_CURRENT_STATE_LOCATION: |
| + ia2_attributes_.push_back(L"current:location"); |
| + break; |
| + case ui::AX_ARIA_CURRENT_STATE_DATE: |
| + ia2_attributes_.push_back(L"current:date"); |
| + break; |
| + case ui::AX_ARIA_CURRENT_STATE_TIME: |
| + ia2_attributes_.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) { |
| + ia2_attributes_.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(ui::AX_ATTR_ARIA_COLUMN_COUNT, "colcount"); |
| + IntAttributeToIA2(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(ui::AX_ATTR_ARIA_CELL_COLUMN_INDEX, "colindex"); |
| + IntAttributeToIA2(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: |
| + ia2_attributes_.push_back(L"sort:none"); |
| + break; |
| + case ui::AX_SORT_DIRECTION_ASCENDING: |
| + ia2_attributes_.push_back(L"sort:ascending"); |
| + break; |
| + case ui::AX_SORT_DIRECTION_DESCENDING: |
| + ia2_attributes_.push_back(L"sort:descending"); |
| + break; |
| + case ui::AX_SORT_DIRECTION_OTHER: |
| + ia2_attributes_.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); |
| + ia2_attributes_.push_back(L"colspan:" + colspan); |
| + } |
| + // Expose rowspan attribute. |
| + base::string16 rowspan; |
| + if (GetData().GetHtmlAttribute("aria-rowspan", &rowspan)) { |
| + SanitizeStringAttributeForIA2(rowspan, &rowspan); |
| + ia2_attributes_.push_back(L"rowspan:" + rowspan); |
| + } |
| + } |
| + |
| + // Expose slider value. |
| + if (IsRangeValueSupported()) { |
| + base::string16 value = GetRangeValueText(); |
| + SanitizeStringAttributeForIA2(value, &value); |
| + ia2_attributes_.push_back(L"valuetext:" + value); |
| + } |
| + |
| + // Expose dropeffect attribute. |
| + base::string16 drop_effect; |
| + if (GetData().GetHtmlAttribute("aria-dropeffect", &drop_effect)) { |
| + SanitizeStringAttributeForIA2(drop_effect, &drop_effect); |
| + ia2_attributes_.push_back(L"dropeffect:" + drop_effect); |
| + } |
| + |
| + // Expose grabbed attribute. |
| + base::string16 grabbed; |
| + if (GetData().GetHtmlAttribute("aria-grabbed", &grabbed)) { |
| + SanitizeStringAttributeForIA2(grabbed, &grabbed); |
| + ia2_attributes_.push_back(L"grabbed:" + grabbed); |
| + } |
| + |
| + // Expose class attribute. |
| + base::string16 class_attr; |
| + if (GetData().GetHtmlAttribute("class", &class_attr)) { |
| + SanitizeStringAttributeForIA2(class_attr, &class_attr); |
| + ia2_attributes_.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); |
| + ia2_attributes_.push_back(L"datetime:" + datetime); |
| + } |
| + |
| + // Expose id attribute. |
| + base::string16 id; |
| + if (GetData().GetHtmlAttribute("id", &id)) { |
| + SanitizeStringAttributeForIA2(id, &id); |
| + ia2_attributes_.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); |
| + ia2_attributes_.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); |
| + ia2_attributes_.push_back(L"text-input-type:" + type); |
| + } |
| + |
| + return ia2_attributes_; |
| +} |
| + |
| bool AXPlatformNodeWin::ShouldNodeHaveReadonlyState( |
| const AXNodeData& data) const { |
| if (data.GetBoolAttribute(ui::AX_ATTR_ARIA_READONLY)) |