diff options
author | dmazzoni@google.com <dmazzoni@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-29 15:39:19 +0000 |
---|---|---|
committer | dmazzoni@google.com <dmazzoni@google.com@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-04-29 15:39:19 +0000 |
commit | 02539ee68c9e076e9e947c40e3a5fdd807480375 (patch) | |
tree | 8191439622be2a0aa7d4e7e42574307da68969e0 /content/browser | |
parent | 6db305c4ce03efa03d4546ae8fc4b1d91faaccef (diff) | |
download | chromium_src-02539ee68c9e076e9e947c40e3a5fdd807480375.zip chromium_src-02539ee68c9e076e9e947c40e3a5fdd807480375.tar.gz chromium_src-02539ee68c9e076e9e947c40e3a5fdd807480375.tar.bz2 |
Implement all missing table accessibility methods on Mac.
DumpAccessibilityTree tests capture index, rowspan, and colspan.
Manual testing with VoiceOver confirms that:
* VoiceOver will now focus a focusable control inside a table
by default.
* Moving between cells in a table reads out the headers too.
BUG=173681,231802
R=aboxhall@chromium.org
Review URL: https://codereview.chromium.org/13932035
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@197046 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'content/browser')
4 files changed, 298 insertions, 6 deletions
diff --git a/content/browser/accessibility/accessibility_tree_formatter_mac.mm b/content/browser/accessibility/accessibility_tree_formatter_mac.mm index 1f281ed3f..4e25596 100644 --- a/content/browser/accessibility/accessibility_tree_formatter_mac.mm +++ b/content/browser/accessibility/accessibility_tree_formatter_mac.mm @@ -8,6 +8,7 @@ #include "base/basictypes.h" #include "base/files/file_path.h" +#include "base/json/json_writer.h" #include "base/stringprintf.h" #include "base/strings/sys_string_conversions.h" #include "base/utf_string_conversions.h" @@ -30,6 +31,8 @@ const char* kYCoordDictAttr = "y"; const char* kSizeDictAttr = "size"; const char* kWidthDictAttr = "width"; const char* kHeightDictAttr = "height"; +const char* kRangeLocDictAttr = "loc"; +const char* kRangeLenDictAttr = "len"; scoped_ptr<DictionaryValue> PopulatePosition(const BrowserAccessibility& node) { scoped_ptr<DictionaryValue> position(new DictionaryValue); @@ -65,6 +68,20 @@ PopulateSize(const BrowserAccessibilityCocoa* cocoa_node) { return size.Pass(); } +scoped_ptr<DictionaryValue> PopulateRange(NSRange range) { + scoped_ptr<DictionaryValue> rangeDict(new DictionaryValue); + rangeDict->SetInteger(kRangeLocDictAttr, static_cast<int>(range.location)); + rangeDict->SetInteger(kRangeLenDictAttr, static_cast<int>(range.length)); + return rangeDict.Pass(); +} + +// Returns true if |value| is an NSValue containing a NSRange. +bool IsRangeValue(id value) { + if (![value isKindOfClass:[NSValue class]]) + return false; + return 0 == strcmp([value objCType], @encode(NSRange)); +} + NSArray* BuildAllAttributesArray() { return [NSArray arrayWithObjects: NSAccessibilityRoleDescriptionAttribute, @@ -83,13 +100,16 @@ NSArray* BuildAllAttributesArray() { @"AXARIABusy", @"AXARIALive", @"AXARIARelevant", + NSAccessibilityColumnIndexRangeAttribute, NSAccessibilityEnabledAttribute, NSAccessibilityFocusedAttribute, + NSAccessibilityIndexAttribute, @"AXLoaded", @"AXLoadingProcess", NSAccessibilityNumberOfCharactersAttribute, NSAccessibilityOrientationAttribute, @"AXRequired", + NSAccessibilityRowIndexRangeAttribute, NSAccessibilityURLAttribute, NSAccessibilityVisibleCharacterRangeAttribute, @"AXVisited", @@ -125,7 +145,11 @@ void AccessibilityTreeFormatter::AddProperties(const BrowserAccessibility& node, continue; } id value = [cocoa_node accessibilityAttributeValue:requestedAttribute]; - if (value != nil) { + if (IsRangeValue(value)) { + dict->Set( + SysNSStringToUTF8(requestedAttribute), + PopulateRange([value rangeValue]).release()); + } else if (value != nil) { dict->SetString( SysNSStringToUTF8(requestedAttribute), SysNSStringToUTF16([NSString stringWithFormat:@"%@", value])); @@ -157,6 +181,17 @@ string16 AccessibilityTreeFormatter::ToString(const DictionaryValue& dict, CR_DEFINE_STATIC_LOCAL(NSArray*, all_attributes, (BuildAllAttributesArray())); for (NSString* requestedAttribute in all_attributes) { string requestedAttributeUTF8 = SysNSStringToUTF8(requestedAttribute); + const DictionaryValue* d_value; + if (dict.GetDictionary(requestedAttributeUTF8, &d_value)) { + std::string json_value; + base::JSONWriter::Write(d_value, &json_value); + WriteAttribute( + [defaultAttributes containsObject:requestedAttribute], + StringPrintf("%s=%s", + requestedAttributeUTF8.c_str(), + json_value.c_str()), + &line); + } if (!dict.GetString(requestedAttributeUTF8, &s_value)) continue; WriteAttribute([defaultAttributes containsObject:requestedAttribute], diff --git a/content/browser/accessibility/browser_accessibility_cocoa.h b/content/browser/accessibility/browser_accessibility_cocoa.h index 9733e0c..0d401a0 100644 --- a/content/browser/accessibility/browser_accessibility_cocoa.h +++ b/content/browser/accessibility/browser_accessibility_cocoa.h @@ -52,6 +52,8 @@ @property(nonatomic, readonly) NSString* ariaRelevant; @property(nonatomic, readonly) NSArray* children; @property(nonatomic, readonly) NSArray* columns; +@property(nonatomic, readonly) NSArray* columnHeaders; +@property(nonatomic, readonly) NSValue* columnIndexRange; @property(nonatomic, readonly) NSString* description; @property(nonatomic, readonly) NSNumber* disclosing; @property(nonatomic, readonly) id disclosedByRow; @@ -63,6 +65,8 @@ // isIgnored returns whether or not the accessibility object // should be ignored by the accessibility hierarchy. @property(nonatomic, readonly, getter=isIgnored) BOOL ignored; +// Index of a row, column, or tree item. +@property(nonatomic, readonly) NSNumber* index; @property(nonatomic, readonly) NSString* invalid; @property(nonatomic, readonly) NSNumber* loaded; @property(nonatomic, readonly) NSNumber* loadingProgress; @@ -77,6 +81,8 @@ // is concerned. @property(nonatomic, readonly) NSString* role; @property(nonatomic, readonly) NSString* roleDescription; +@property(nonatomic, readonly) NSArray* rowHeaders; +@property(nonatomic, readonly) NSValue* rowIndexRange; @property(nonatomic, readonly) NSArray* rows; // The size of this object. @property(nonatomic, readonly) NSValue* size; @@ -91,6 +97,9 @@ @property(nonatomic, readonly) NSString* value; @property(nonatomic, readonly) NSString* valueDescription; @property(nonatomic, readonly) NSValue* visibleCharacterRange; +@property(nonatomic, readonly) NSArray* visibleCells; +@property(nonatomic, readonly) NSArray* visibleColumns; +@property(nonatomic, readonly) NSArray* visibleRows; @property(nonatomic, readonly) NSNumber* visited; @property(nonatomic, readonly) id window; @end diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm index b2117bb..f53cb5a 100644 --- a/content/browser/accessibility/browser_accessibility_cocoa.mm +++ b/content/browser/accessibility/browser_accessibility_cocoa.mm @@ -275,6 +275,9 @@ NSDictionary* attributeToMethodNameMap = nil; } attributeToMethodNameContainer[] = { { NSAccessibilityChildrenAttribute, @"children" }, { NSAccessibilityColumnsAttribute, @"columns" }, + { NSAccessibilityColumnHeaderUIElementsAttribute, @"columnHeaders" }, + { NSAccessibilityColumnIndexRangeAttribute, @"columnIndexRange" }, + { NSAccessibilityContentsAttribute, @"contents" }, { NSAccessibilityDescriptionAttribute, @"description" }, { NSAccessibilityDisclosingAttribute, @"disclosing" }, { NSAccessibilityDisclosedByRowAttribute, @"disclosedByRow" }, @@ -282,7 +285,9 @@ NSDictionary* attributeToMethodNameMap = nil; { NSAccessibilityDisclosedRowsAttribute, @"disclosedRows" }, { NSAccessibilityEnabledAttribute, @"enabled" }, { NSAccessibilityFocusedAttribute, @"focused" }, + { NSAccessibilityHeaderAttribute, @"header" }, { NSAccessibilityHelpAttribute, @"help" }, + { NSAccessibilityIndexAttribute, @"index" }, { NSAccessibilityMaxValueAttribute, @"maxValue" }, { NSAccessibilityMinValueAttribute, @"minValue" }, { NSAccessibilityNumberOfCharactersAttribute, @"numberOfCharacters" }, @@ -291,6 +296,8 @@ NSDictionary* attributeToMethodNameMap = nil; { NSAccessibilityPositionAttribute, @"position" }, { NSAccessibilityRoleAttribute, @"role" }, { NSAccessibilityRoleDescriptionAttribute, @"roleDescription" }, + { NSAccessibilityRowHeaderUIElementsAttribute, @"rowHeaders" }, + { NSAccessibilityRowIndexRangeAttribute, @"rowIndexRange" }, { NSAccessibilityRowsAttribute, @"rows" }, { NSAccessibilitySizeAttribute, @"size" }, { NSAccessibilitySubroleAttribute, @"subrole" }, @@ -302,6 +309,9 @@ NSDictionary* attributeToMethodNameMap = nil; { NSAccessibilityValueAttribute, @"value" }, { NSAccessibilityValueDescriptionAttribute, @"valueDescription" }, { NSAccessibilityVisibleCharacterRangeAttribute, @"visibleCharacterRange" }, + { NSAccessibilityVisibleCellsAttribute, @"visibleCells" }, + { NSAccessibilityVisibleColumnsAttribute, @"visibleColumns" }, + { NSAccessibilityVisibleRowsAttribute, @"visibleRows" }, { NSAccessibilityWindowAttribute, @"window" }, { @"AXAccessKey", @"accessKey" }, { @"AXARIAAtomic", @"ariaAtomic" }, @@ -424,6 +434,40 @@ NSDictionary* attributeToMethodNameMap = nil; } } +- (NSArray*)columnHeaders { + if ([self internalRole] != AccessibilityNodeData::ROLE_TABLE && + [self internalRole] != AccessibilityNodeData::ROLE_GRID) { + return nil; + } + + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + const std::vector<int32>& uniqueCellIds = + browserAccessibility_->unique_cell_ids(); + for (size_t i = 0; i < uniqueCellIds.size(); ++i) { + int id = uniqueCellIds[i]; + BrowserAccessibility* cell = + browserAccessibility_->manager()->GetFromRendererID(id); + if (cell && cell->role() == AccessibilityNodeData::ROLE_COLUMN_HEADER) + [ret addObject:cell->ToBrowserAccessibilityCocoa()]; + } + return ret; +} + +- (NSValue*)columnIndexRange { + if ([self internalRole] != AccessibilityNodeData::ROLE_CELL) + return nil; + + int column = -1; + int colspan = -1; + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, &column); + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_SPAN, &colspan); + if (column >= 0 && colspan >= 1) + return [NSValue valueWithRange:NSMakeRange(column, colspan)]; + return nil; +} + - (NSArray*)columns { NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; for (BrowserAccessibilityCocoa* child in [self children]) { @@ -517,12 +561,53 @@ NSDictionary* attributeToMethodNameMap = nil; return ret; } +- (id)header { + int headerElementId = -1; + if ([self internalRole] == AccessibilityNodeData::ROLE_TABLE || + [self internalRole] == AccessibilityNodeData::ROLE_GRID) { + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_HEADER_ID, &headerElementId); + } else if ([self internalRole] == AccessibilityNodeData::ROLE_COLUMN) { + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_HEADER_ID, &headerElementId); + } else if ([self internalRole] == AccessibilityNodeData::ROLE_ROW) { + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_HEADER_ID, &headerElementId); + } + + if (headerElementId > 0) { + BrowserAccessibility* headerObject = + browserAccessibility_->manager()->GetFromRendererID(headerElementId); + if (headerObject) + return headerObject->ToBrowserAccessibilityCocoa(); + } + return nil; +} + - (NSString*)help { return NSStringForStringAttribute( browserAccessibility_->string_attributes(), AccessibilityNodeData::ATTR_HELP); } +- (NSNumber*)index { + if ([self internalRole] == AccessibilityNodeData::ROLE_COLUMN) { + int columnIndex; + if (browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_INDEX, &columnIndex)) { + return [NSNumber numberWithInt:columnIndex]; + } + } else if ([self internalRole] == AccessibilityNodeData::ROLE_ROW) { + int rowIndex; + if (browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_INDEX, &rowIndex)) { + return [NSNumber numberWithInt:rowIndex]; + } + } + + return nil; +} + // Returns whether or not this node should be ignored in the // accessibility tree. - (BOOL)isIgnored { @@ -679,11 +764,59 @@ NSDictionary* attributeToMethodNameMap = nil; return NSAccessibilityRoleDescription(role, nil); } +- (NSArray*)rowHeaders { + if ([self internalRole] != AccessibilityNodeData::ROLE_TABLE && + [self internalRole] != AccessibilityNodeData::ROLE_GRID) { + return nil; + } + + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + const std::vector<int32>& uniqueCellIds = + browserAccessibility_->unique_cell_ids(); + for (size_t i = 0; i < uniqueCellIds.size(); ++i) { + int id = uniqueCellIds[i]; + BrowserAccessibility* cell = + browserAccessibility_->manager()->GetFromRendererID(id); + if (cell && cell->role() == AccessibilityNodeData::ROLE_ROW_HEADER) + [ret addObject:cell->ToBrowserAccessibilityCocoa()]; + } + return ret; +} + +- (NSValue*)rowIndexRange { + if ([self internalRole] != AccessibilityNodeData::ROLE_CELL) + return nil; + + int row = -1; + int rowspan = -1; + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_INDEX, &row); + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_ROW_SPAN, &rowspan); + if (row >= 0 && rowspan >= 1) + return [NSValue valueWithRange:NSMakeRange(row, rowspan)]; + return nil; +} + - (NSArray*)rows { NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; - for (BrowserAccessibilityCocoa* child in [self children]) { - if ([[child role] isEqualToString:NSAccessibilityRowRole]) - [ret addObject:child]; + + if ([self internalRole] == AccessibilityNodeData::ROLE_TABLE|| + [self internalRole] == AccessibilityNodeData::ROLE_GRID) { + for (BrowserAccessibilityCocoa* child in [self children]) { + if ([[child role] isEqualToString:NSAccessibilityRowRole]) + [ret addObject:child]; + } + } else if ([self internalRole] == AccessibilityNodeData::ROLE_COLUMN) { + const std::vector<int32>& indirectChildIds = + browserAccessibility_->indirect_child_ids(); + for (uint32 i = 0; i < indirectChildIds.size(); ++i) { + int id = indirectChildIds[i]; + BrowserAccessibility* rowElement = + browserAccessibility_->manager()->GetFromRendererID(id); + if (rowElement) + [ret addObject:rowElement->ToBrowserAccessibilityCocoa()]; + } } return ret; @@ -827,6 +960,28 @@ NSDictionary* attributeToMethodNameMap = nil; NSMakeRange(0, browserAccessibility_->value().length())]; } +- (NSArray*)visibleCells { + NSMutableArray* ret = [[[NSMutableArray alloc] init] autorelease]; + const std::vector<int32>& uniqueCellIds = + browserAccessibility_->unique_cell_ids(); + for (size_t i = 0; i < uniqueCellIds.size(); ++i) { + int id = uniqueCellIds[i]; + BrowserAccessibility* cell = + browserAccessibility_->manager()->GetFromRendererID(id); + if (cell) + [ret addObject:cell->ToBrowserAccessibilityCocoa()]; + } + return ret; +} + +- (NSArray*)visibleColumns { + return [self columns]; +} + +- (NSArray*)visibleRows { + return [self rows]; +} + - (NSNumber*)visited { return [NSNumber numberWithBool: GetState(browserAccessibility_, AccessibilityNodeData::STATE_TRAVERSED)]; @@ -914,6 +1069,63 @@ NSDictionary* attributeToMethodNameMap = nil; NSMakeRange(start, end - start)]; } + if ([attribute isEqualToString: + NSAccessibilityCellForColumnAndRowParameterizedAttribute]) { + if ([self internalRole] != AccessibilityNodeData::ROLE_TABLE && + [self internalRole] != AccessibilityNodeData::ROLE_GRID) { + return nil; + } + if (![parameter isKindOfClass:[NSArray self]]) + return nil; + NSArray* array = parameter; + int column = [[array objectAtIndex:0] intValue]; + int row = [[array objectAtIndex:1] intValue]; + int num_columns = 0; + int num_rows = 0; + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_COLUMN_COUNT, &num_columns); + browserAccessibility_->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_COUNT, &num_rows); + if (column < 0 || column >= num_columns || + row < 0 || row >= num_rows) { + return nil; + } + for (size_t i = 0; + i < browserAccessibility_->child_count(); + ++i) { + BrowserAccessibility* child = browserAccessibility_->GetChild(i); + if (child->role() != AccessibilityNodeData::ROLE_ROW) + continue; + int rowIndex; + if (!child->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_ROW_INDEX, &rowIndex)) { + continue; + } + if (rowIndex < row) + continue; + if (rowIndex > row) + break; + for (size_t j = 0; + j < child->child_count(); + ++j) { + BrowserAccessibility* cell = child->GetChild(j); + if (cell->role() != AccessibilityNodeData::ROLE_CELL) + continue; + int colIndex; + if (!cell->GetIntAttribute( + AccessibilityNodeData::ATTR_TABLE_CELL_COLUMN_INDEX, + &colIndex)) { + continue; + } + if (colIndex == column) + return cell->ToBrowserAccessibilityCocoa(); + if (colIndex > column) + break; + } + } + return nil; + } + // TODO(dtseng): support the following attributes. if ([attribute isEqualTo: NSAccessibilityRangeForPositionParameterizedAttribute] || @@ -932,6 +1144,12 @@ NSDictionary* attributeToMethodNameMap = nil; // Returns an array of parameterized attributes names that this object will // respond to. - (NSArray*)accessibilityParameterizedAttributeNames { + if ([[self role] isEqualToString:NSAccessibilityTableRole] || + [[self role] isEqualToString:NSAccessibilityGridRole]) { + return [NSArray arrayWithObjects: + NSAccessibilityCellForColumnAndRowParameterizedAttribute, + nil]; + } if ([[self role] isEqualToString:NSAccessibilityTextFieldRole] || [[self role] isEqualToString:NSAccessibilityTextAreaRole]) { return [NSArray arrayWithObjects: @@ -1023,10 +1241,29 @@ NSDictionary* attributeToMethodNameMap = nil; // Specific role attributes. NSString* role = [self role]; NSString* subrole = [self subrole]; - if ([role isEqualToString:NSAccessibilityTableRole]) { + if ([role isEqualToString:NSAccessibilityTableRole] || + [role isEqualToString:NSAccessibilityGridRole]) { [ret addObjectsFromArray:[NSArray arrayWithObjects: NSAccessibilityColumnsAttribute, + NSAccessibilityVisibleColumnsAttribute, + NSAccessibilityRowsAttribute, + NSAccessibilityVisibleRowsAttribute, + NSAccessibilityVisibleCellsAttribute, + NSAccessibilityHeaderAttribute, + NSAccessibilityColumnHeaderUIElementsAttribute, + NSAccessibilityRowHeaderUIElementsAttribute, + nil]]; + } else if ([role isEqualToString:NSAccessibilityColumnRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityIndexAttribute, + NSAccessibilityHeaderAttribute, NSAccessibilityRowsAttribute, + NSAccessibilityVisibleRowsAttribute, + nil]]; + } else if ([role isEqualToString:NSAccessibilityCellRole]) { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityColumnIndexRangeAttribute, + NSAccessibilityRowIndexRangeAttribute, nil]]; } else if ([role isEqualToString:@"AXWebArea"]) { [ret addObjectsFromArray:[NSArray arrayWithObjects: @@ -1073,11 +1310,14 @@ NSDictionary* attributeToMethodNameMap = nil; NSAccessibilityDisclosureLevelAttribute, NSAccessibilityDisclosedRowsAttribute, nil]]; + } else { + [ret addObjectsFromArray:[NSArray arrayWithObjects: + NSAccessibilityIndexAttribute, + nil]]; } } } - // Live regions. string16 s; if (browserAccessibility_->GetStringAttribute( diff --git a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc index 882eaa5..08635d2 100644 --- a/content/browser/accessibility/dump_accessibility_tree_browsertest.cc +++ b/content/browser/accessibility/dump_accessibility_tree_browsertest.cc @@ -437,6 +437,14 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTab) { RunTest(FILE_PATH_LITERAL("tab.html")); } +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTableSimple) { + RunTest(FILE_PATH_LITERAL("table-simple.html")); +} + +IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityTableSpans) { + RunTest(FILE_PATH_LITERAL("table-spans.html")); +} + IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityToggleButton) { RunTest(FILE_PATH_LITERAL("togglebutton.html")); |