Commit 837c2040 authored by dmazzoni's avatar dmazzoni Committed by Commit bot

Mac accessibility: only use AXTitleUIElement if that element has valid text.

On Mac OS X, the accessible name of an object can be exposed in three
different ways:

AXTitle: for visible text
AXDescription: for accessible text (like image "alt" or aria-label)
AXTitleUIElement: for controls labeled by another element

VoiceOver is kind of picky, it only wants us to expose just one of the
three, not multiple ones, but to be more confusing it ignores
AXTitleUIElement if it's not a control.

The bug here was that there was an obscure case where a checkbox's
name came from a label, but the label's text was "hidden" by a
heuristic that tries to combine the checkbox and its text into a
single element for simplicity. All of that is fine, but we were
exposing AXTitleUIElement for the checkbox when the element it
pointed to had empty text.

The fix is just to make sure the element pointed to by
AXTitleUIElement actually has text, otherwise we fall back on
AXTitle.

BUG=646846

Review-Url: https://codereview.chromium.org/2348663002
Cr-Commit-Position: refs/heads/master@{#419313}
parent ed437141
...@@ -772,24 +772,15 @@ NSString* const NSAccessibilityRequiredAttribute = @"AXRequired"; ...@@ -772,24 +772,15 @@ NSString* const NSAccessibilityRequiredAttribute = @"AXRequired";
if ([self shouldExposeNameInAXValue]) if ([self shouldExposeNameInAXValue])
return @""; return @"";
// If the name came from a single related element and it's present in the // If we're exposing the title in TitleUIElement, don't also redundantly
// tree, it will be exposed in AXTitleUIElement. // expose it in AXDescription.
std::vector<int32_t> labelledby_ids = if ([self shouldExposeTitleUIElement])
browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS); return @"";
ui::AXNameFrom nameFrom = static_cast<ui::AXNameFrom>( ui::AXNameFrom nameFrom = static_cast<ui::AXNameFrom>(
browserAccessibility_->GetIntAttribute(ui::AX_ATTR_NAME_FROM)); browserAccessibility_->GetIntAttribute(ui::AX_ATTR_NAME_FROM));
std::string name = browserAccessibility_->GetStringAttribute( std::string name = browserAccessibility_->GetStringAttribute(
ui::AX_ATTR_NAME); ui::AX_ATTR_NAME);
// VoiceOver ignores titleUIElement on non-control AX nodes, so this special
// case expressly returns a nonempty text name for these nodes.
if (nameFrom == ui::AX_NAME_FROM_RELATED_ELEMENT &&
labelledby_ids.size() == 1 &&
browserAccessibility_->manager()->GetFromID(labelledby_ids[0]) &&
!browserAccessibility_->IsControl()) {
return base::SysUTF8ToNSString(name);
}
if (!name.empty()) { if (!name.empty()) {
// On Mac OS X, the accessible name of an object is exposed as its // On Mac OS X, the accessible name of an object is exposed as its
// title if it comes from visible text, and as its description // title if it comes from visible text, and as its description
...@@ -1237,6 +1228,35 @@ NSString* const NSAccessibilityRequiredAttribute = @"AXRequired"; ...@@ -1237,6 +1228,35 @@ NSString* const NSAccessibilityRequiredAttribute = @"AXRequired";
} }
} }
// Returns true if this object should expose its accessible name using
// AXTitleUIElement rather than AXTitle or AXDescription. We only do
// this if it's a control, if there's a single label, and the label has
// nonempty text.
// internal
- (BOOL)shouldExposeTitleUIElement {
// VoiceOver ignores TitleUIElement if the element isn't a control.
if (!browserAccessibility_->IsControl())
return false;
ui::AXNameFrom nameFrom = static_cast<ui::AXNameFrom>(
browserAccessibility_->GetIntAttribute(ui::AX_ATTR_NAME_FROM));
if (nameFrom != ui::AX_NAME_FROM_RELATED_ELEMENT)
return false;
std::vector<int32_t> labelledby_ids =
browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS);
if (labelledby_ids.size() != 1)
return false;
BrowserAccessibility* label =
browserAccessibility_->manager()->GetFromID(labelledby_ids[0]);
if (!label)
return false;
std::string labelName = label->GetStringAttribute(ui::AX_ATTR_NAME);
return !labelName.empty();
}
// internal // internal
- (content::BrowserAccessibilityDelegate*)delegate { - (content::BrowserAccessibilityDelegate*)delegate {
return [self instanceActive] ? browserAccessibility_->manager()->delegate() return [self instanceActive] ? browserAccessibility_->manager()->delegate()
...@@ -1716,21 +1736,16 @@ NSString* const NSAccessibilityRequiredAttribute = @"AXRequired"; ...@@ -1716,21 +1736,16 @@ NSString* const NSAccessibilityRequiredAttribute = @"AXRequired";
if ([self shouldExposeNameInAXValue]) if ([self shouldExposeNameInAXValue])
return @""; return @"";
// If the name came from a single related element and it's present in the // If we're exposing the title in TitleUIElement, don't also redundantly
// tree, it will be exposed in AXTitleUIElement. // expose it in AXDescription.
std::vector<int32_t> labelledby_ids = if ([self shouldExposeTitleUIElement])
browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS);
ui::AXNameFrom nameFrom = static_cast<ui::AXNameFrom>(
browserAccessibility_->GetIntAttribute(ui::AX_ATTR_NAME_FROM));
if (nameFrom == ui::AX_NAME_FROM_RELATED_ELEMENT &&
labelledby_ids.size() == 1 &&
browserAccessibility_->manager()->GetFromID(labelledby_ids[0])) {
return @""; return @"";
}
// On Mac OS X, the accessible name of an object is exposed as its // On Mac OS X, the accessible name of an object is exposed as its
// title if it comes from visible text, and as its description // title if it comes from visible text, and as its description
// otherwise, but never both. // otherwise, but never both.
ui::AXNameFrom nameFrom = static_cast<ui::AXNameFrom>(
browserAccessibility_->GetIntAttribute(ui::AX_ATTR_NAME_FROM));
if (nameFrom == ui::AX_NAME_FROM_CONTENTS || if (nameFrom == ui::AX_NAME_FROM_CONTENTS ||
nameFrom == ui::AX_NAME_FROM_RELATED_ELEMENT || nameFrom == ui::AX_NAME_FROM_RELATED_ELEMENT ||
nameFrom == ui::AX_NAME_FROM_VALUE) { nameFrom == ui::AX_NAME_FROM_VALUE) {
...@@ -1744,6 +1759,9 @@ NSString* const NSAccessibilityRequiredAttribute = @"AXRequired"; ...@@ -1744,6 +1759,9 @@ NSString* const NSAccessibilityRequiredAttribute = @"AXRequired";
- (id)titleUIElement { - (id)titleUIElement {
if (![self instanceActive]) if (![self instanceActive])
return nil; return nil;
if (![self shouldExposeTitleUIElement])
return nil;
std::vector<int32_t> labelledby_ids = std::vector<int32_t> labelledby_ids =
browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS); browserAccessibility_->GetIntListAttribute(ui::AX_ATTR_LABELLEDBY_IDS);
ui::AXNameFrom nameFrom = static_cast<ui::AXNameFrom>( ui::AXNameFrom nameFrom = static_cast<ui::AXNameFrom>(
......
...@@ -970,6 +970,11 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, ...@@ -970,6 +970,11 @@ IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
RunHtmlTest(FILE_PATH_LITERAL("input-checkbox-in-menu.html")); RunHtmlTest(FILE_PATH_LITERAL("input-checkbox-in-menu.html"));
} }
IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest,
AccessibilityInputCheckBoxLabel) {
RunHtmlTest(FILE_PATH_LITERAL("input-checkbox-label.html"));
}
IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityInputColor) { IN_PROC_BROWSER_TEST_F(DumpAccessibilityTreeTest, AccessibilityInputColor) {
RunHtmlTest(FILE_PATH_LITERAL("input-color.html")); RunHtmlTest(FILE_PATH_LITERAL("input-color.html"));
} }
......
...@@ -19,7 +19,7 @@ AXWebArea AXRoleDescription='HTML content' ...@@ -19,7 +19,7 @@ AXWebArea AXRoleDescription='HTML content'
' '
++++AXRadioButton AXRoleDescription='radio button' AXValue='0' AXARIASetSize='2' AXARIAPosInSet='2' ++++AXRadioButton AXRoleDescription='radio button' AXValue='0' AXARIASetSize='2' AXARIAPosInSet='2'
++++AXStaticText AXRoleDescription='text' AXValue='Banana' ++++AXStaticText AXRoleDescription='text' AXValue='Banana'
++AXGroup AXRoleDescription='group' AXDescription='Cake' AXTitleUIElement='AXGroup' ++AXGroup AXRoleDescription='group' AXTitle='Cake'
++++AXGroup AXRoleDescription='group' ++++AXGroup AXRoleDescription='group'
++++++AXStaticText AXRoleDescription='text' AXValue='Cake' ++++++AXStaticText AXRoleDescription='text' AXValue='Cake'
++++AXGroup AXRoleDescription='group' ++++AXGroup AXRoleDescription='group'
...@@ -36,4 +36,4 @@ AXWebArea AXRoleDescription='HTML content' ...@@ -36,4 +36,4 @@ AXWebArea AXRoleDescription='HTML content'
' '
++++++AXRadioButton AXRoleDescription='radio button' AXTitle='blue' AXValue='0' AXARIASetSize='1' AXARIAPosInSet='1' ++++++AXRadioButton AXRoleDescription='radio button' AXTitle='blue' AXValue='0' AXARIASetSize='1' AXARIAPosInSet='1'
++AXGroup AXRoleDescription='group' ++AXGroup AXRoleDescription='group'
++++AXStaticText AXRoleDescription='text' AXValue='Done' ++++AXStaticText AXRoleDescription='text' AXValue='Done'
\ No newline at end of file
AXWebArea AXWebArea
++AXTable AXDescription='Browser and Engine' AXTitleUIElement='AXGroup' ++AXTable AXTitle='Browser and Engine'
++++AXGroup ++++AXGroup
++++++AXStaticText AXValue='Browser and Engine' ++++++AXStaticText AXValue='Browser and Engine'
++++AXRow ++++AXRow
......
AXWebArea AXWebArea
++AXGroup ++AXGroup
++++AXGroup AXDescription='Browser Engines:' AXTitleUIElement='AXGroup' ++++AXGroup AXTitle='Browser Engines:'
++++++AXGroup ++++++AXGroup
++++++++AXStaticText AXValue='Browser Engines:' ++++++++AXStaticText AXValue='Browser Engines:'
AXWebArea AXWebArea
++AXGroup AXDescription='Fig.1 - A green Box' AXTitleUIElement='AXGroup' ++AXGroup AXTitle='Fig.1 - A green Box'
++++AXGroup ++++AXGroup
++++++AXImage AXDescription='This is a green box.' ++++++AXImage AXDescription='This is a green box.'
++++AXGroup ++++AXGroup
......
AXWebArea
++AXGroup
++++AXGroup
++++++AXCheckBox AXTitle=' Checkbox Title' AXValue='0'
<html>
<body>
<label style="position: relative;">
<input type="checkbox">
Checkbox Title
</label>
</body>
</html>
AXWebArea AXWebArea
++AXGroup ++AXGroup
++++AXGroup AXDescription='Browser Engines:' AXTitleUIElement='AXGroup' ++++AXGroup AXTitle='Browser Engines:'
++++++AXGroup ++++++AXGroup
++++++++AXStaticText AXValue='Browser Engines:' ++++++++AXStaticText AXValue='Browser Engines:'
++++++AXGroup ++++++AXGroup
......
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