Commit 03ed812f authored by Victor Fei's avatar Victor Fei Committed by Commit Bot

Reland "A11y:Expose labels of radio & checkbox when focused."

This is a reland of 9612f58a

The change was reverted because of failure on linux-trusty-rel due to
a mismatch in dump test result for "checkable" state.
In this reland, I added a filter @AURALINUX-DENY:checkable to fix this
test mismatch. This filter seems to accompany all other radio button dump
tests for linux platform.

Original change's description:
> A11y:Expose labels of radio & checkbox when focused.
>
> Currently, when a label is referenced by a control of type radio or
> checkbox, we set the label as ignored in the AXTree thereby excluding
> it from being perceived by ATs.
> However, for the case when the control (radio/checkbox) is a
> descendant of the label and label is focusable (tabindex="0") but
> control is to be omitted from focus (tabindex="-1"), ATs perceive
> neither the label nor the control when tabbing through the content:
>
>     <label style="display:block" tabindex="0">
>       radio button 1
>       <input type="radio" id="radio1" tabindex="-1">
>     </label>
>
>     <label style="display:block" for="radio2" tabindex="0">
>       radio button 2
>       <input type="radio" id="radio2" tabindex="-1">
>     </label>
>
>     <label style="display:block" for="checkbox1" tabindex="0">
>       checkbox 1
>       <input type="checkbox" id="checkbox1" tabindex="-1">
>     </label>
>
>     <label style="display:block" for="checkbox2" tabindex="0">
>       checkbox 2
>       <input type="checkbox" id="checkbox2" tabindex="-1">
>     </label>
>
> This change fixes the above scenario by exposing labels (referenced by
> radio or checkbox) when they are focusable. For all other cases when
> labels are referenced by radio or checkbox, we keep the existing
> behavior of ignoring the label.
>
> Note: Neither Firefox nor Edge legacy hides a label from AT when the
> label is referenced by a control. On Firefox and Edge legacy, label
> content will be read twice (focus on label and focus on control
> respectively).
> WAI-ARIA spec does not specify label should be ignored when associated
> with a radio/checkbox control.
>
> Bug: 1040210
> Change-Id: Ie7df5144dd2f0d285d97e6556adb46dcf2338f25
> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1992017
> Commit-Queue: Victor Fei <vicfei@microsoft.com>
> Reviewed-by: Dominic Mazzoni <dmazzoni@chromium.org>
> Cr-Commit-Position: refs/heads/master@{#731820}

Bug: 1040210
Change-Id: I09243796ec3affe648d46ec64d7dd20e20c5893f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2002892Reviewed-by: default avatarKurt Catti-Schmidt <kschmi@microsoft.com>
Reviewed-by: default avatarDominic Mazzoni <dmazzoni@chromium.org>
Commit-Queue: Victor Fei <vicfei@microsoft.com>
Cr-Commit-Position: refs/heads/master@{#732931}
parent 6b494c0a
...@@ -1661,6 +1661,11 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityInputRadio) { ...@@ -1661,6 +1661,11 @@ IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, AccessibilityInputRadio) {
RunHtmlTest(FILE_PATH_LITERAL("input-radio.html")); RunHtmlTest(FILE_PATH_LITERAL("input-radio.html"));
} }
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
AccessibilityInputRadioCheckboxLabel) {
RunHtmlTest(FILE_PATH_LITERAL("input-radio-checkbox-label.html"));
}
IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest, IN_PROC_BROWSER_TEST_P(DumpAccessibilityTreeTest,
AccessibilityInputRadioInMenu) { AccessibilityInputRadioInMenu) {
RunHtmlTest(FILE_PATH_LITERAL("input-radio-in-menu.html")); RunHtmlTest(FILE_PATH_LITERAL("input-radio-in-menu.html"));
......
android.webkit.WebView focusable focused scrollable
++android.view.View
++++android.widget.RadioButton role_description='radio button' checkable clickable focusable name='label ignored for radio button'
++++android.widget.CheckBox role_description='checkbox' checkable clickable focusable name='label ignored for checkbox'
++++android.view.View focusable name='label exposed for radio button '
++++++android.view.View name='label exposed for radio button '
++++++android.widget.RadioButton role_description='radio button' checkable clickable focusable name='label exposed for radio button' item_index=1 row_index=1
++++++android.view.View name=' '
++++android.view.View focusable name='label exposed for checkbox '
++++++android.view.View name='label exposed for checkbox '
++++++android.widget.CheckBox role_description='checkbox' checkable clickable focusable name='label exposed for checkbox'
++++++android.view.View name=' '
[document web]
++[section]
++++[radio button] name='label ignored for radio button' checkable:true
++++[check box] name='label ignored for checkbox' checkable:true
++++[label] name='label exposed for radio button ' label-for
++++++[static] name='label exposed for radio button '
++++++[radio button] name='label exposed for radio button' labelled-by checkable:true
++++++[static] name=' '
++++[label] name='label exposed for checkbox ' label-for
++++++[static] name='label exposed for checkbox '
++++++[check box] name='label exposed for checkbox' labelled-by checkable:true
++++++[static] name=' '
rootWebArea
++genericContainer
++++radioButton name='label ignored for radio button' checkedState=false
++++checkBox name='label ignored for checkbox' checkedState=false
++++labelText name='label exposed for radio button '
++++++staticText name='label exposed for radio button '
++++++++inlineTextBox name='label exposed for radio button '
++++++radioButton name='label exposed for radio button' checkedState=false
++++++staticText name=' '
++++++++inlineTextBox name=' '
++++labelText name='label exposed for checkbox '
++++++staticText name='label exposed for checkbox '
++++++++inlineTextBox name='label exposed for '
++++++++inlineTextBox name='checkbox '
++++++checkBox name='label exposed for checkbox' checkedState=false
++++++staticText name=' '
AXWebArea
++AXGroup
++++AXRadioButton AXTitle='label ignored for radio button' AXValue='0'
++++AXCheckBox AXTitle='label ignored for checkbox' AXValue='0'
++++AXGroup AXTitle='label exposed for radio button '
++++++AXStaticText AXValue='label exposed for radio button '
++++++AXRadioButton AXValue='0' AXTitleUIElement='AXGroup label exposed for radio button '
++++++AXStaticText AXValue=' '
++++AXGroup AXTitle='label exposed for checkbox '
++++++AXStaticText AXValue='label exposed for checkbox '
++++++AXCheckBox AXValue='0' AXTitleUIElement='AXGroup label exposed for checkbox '
++++++AXStaticText AXValue=' '
document
++group IsControlElement=false
++++radio Name='label ignored for radio button' SelectionItem.IsSelected=false
++++checkbox Name='label ignored for checkbox' Toggle.ToggleState='Off'
++++description Name='label exposed for radio button '
++++++description Name='label exposed for radio button '
++++++radio Name='label exposed for radio button' SelectionItem.IsSelected=false
++++++description Name=' '
++++description Name='label exposed for checkbox '
++++++description Name='label exposed for checkbox '
++++++checkbox Name='label exposed for checkbox' Toggle.ToggleState='Off'
++++++description Name=' '
ROLE_SYSTEM_DOCUMENT READONLY FOCUSABLE
++IA2_ROLE_SECTION
++++ROLE_SYSTEM_RADIOBUTTON name='label ignored for radio button' FOCUSABLE IA2_STATE_CHECKABLE checkable:true
++++ROLE_SYSTEM_CHECKBUTTON name='label ignored for checkbox' FOCUSABLE IA2_STATE_CHECKABLE checkable:true
++++IA2_ROLE_LABEL name='label exposed for radio button ' FOCUSABLE
++++++ROLE_SYSTEM_STATICTEXT name='label exposed for radio button '
++++++ROLE_SYSTEM_RADIOBUTTON name='label exposed for radio button' FOCUSABLE IA2_STATE_CHECKABLE checkable:true
++++++ROLE_SYSTEM_STATICTEXT name=' '
++++IA2_ROLE_LABEL name='label exposed for checkbox ' FOCUSABLE
++++++ROLE_SYSTEM_STATICTEXT name='label exposed for checkbox '
++++++ROLE_SYSTEM_CHECKBUTTON name='label exposed for checkbox' FOCUSABLE IA2_STATE_CHECKABLE checkable:true
++++++ROLE_SYSTEM_STATICTEXT name=' '
<!--
@AURALINUX-DENY:checkable
-->
<!DOCTYPE html>
<html>
<body>
<body>
<!-- When the radio button's associated label is not set to be
focusable, it will be ignored from the accessibility tree. -->
<label for="radio1">
label ignored for radio button
<input type="radio" id="radio1">
</label>
<!-- When the checkbox's associated label is not set to be
focusable, it will be ignored from the accessibility tree. -->
<label for="checkbox1">
label ignored for checkbox
<input type="checkbox" id="checkbox1">
</label>
<!-- When the radio button's associated label is set to be focusable,
it will be included in the accessibility tree. -->
<label for="radio2" tabindex="0">
label exposed for radio button
<input type="radio" id="radio2" tabindex="-1">
</label>
<!-- When the checkbox's associated label is set to be focusable,
it will be included in the accessibility tree. -->
<label for="checkbox2" tabindex="0">
label exposed for checkbox
<input type="checkbox" id="checkbox2" tabindex="-1">
</label>
</body>
</html>
...@@ -1735,7 +1735,8 @@ AXObject* AXLayoutObject::AccessibilityHitTest(const IntPoint& point) const { ...@@ -1735,7 +1735,8 @@ AXObject* AXLayoutObject::AccessibilityHitTest(const IntPoint& point) const {
// control. // control.
if (result->IsAXLayoutObject()) { if (result->IsAXLayoutObject()) {
AXObject* control_object = AXObject* control_object =
ToAXLayoutObject(result)->CorrespondingControlForLabelElement(); ToAXLayoutObject(result)
->CorrespondingControlAXObjectForLabelElement();
if (control_object && control_object->NameFromLabelElement()) if (control_object && control_object->NameFromLabelElement())
return control_object; return control_object;
} }
......
...@@ -178,21 +178,24 @@ AXObjectInclusion AXNodeObject::ShouldIncludeBasedOnSemantics( ...@@ -178,21 +178,24 @@ AXObjectInclusion AXNodeObject::ShouldIncludeBasedOnSemantics(
if (IsTableLikeRole() || IsTableRowLikeRole() || IsTableCellLikeRole()) if (IsTableLikeRole() || IsTableRowLikeRole() || IsTableCellLikeRole())
return kIncludeObject; return kIncludeObject;
// Ignore labels that are already referenced by a control. // Ignore labels that are already referenced by a control but are not set to
AXObject* control_object = CorrespondingControlForLabelElement(); // be focusable.
HTMLLabelElement* label = LabelElementContainer(); AXObject* control_ax_object = CorrespondingControlAXObjectForLabelElement();
if (control_object && control_object->IsCheckboxOrRadio() && if (control_ax_object && control_ax_object->IsCheckboxOrRadio() &&
control_object->NameFromLabelElement() && control_ax_object->NameFromLabelElement() &&
AccessibleNode::GetPropertyOrARIAAttribute( AccessibleNode::GetPropertyOrARIAAttribute(
label, AOMStringProperty::kRole) == g_null_atom) { LabelElementContainer(), AOMStringProperty::kRole) == g_null_atom) {
AXObject* label_ax_object = CorrespondingLabelAXObject();
// If the label is set to be focusable, we should expose it.
if (label_ax_object && label_ax_object->CanSetFocusAttribute())
return kIncludeObject;
if (ignored_reasons) { if (ignored_reasons) {
if (label && label != GetNode()) { if (label_ax_object && label_ax_object != this)
AXObject* label_ax_object = AXObjectCache().GetOrCreate(label);
ignored_reasons->push_back( ignored_reasons->push_back(
IgnoredReason(kAXLabelContainer, label_ax_object)); IgnoredReason(kAXLabelContainer, label_ax_object));
}
ignored_reasons->push_back(IgnoredReason(kAXLabelFor, control_object)); ignored_reasons->push_back(IgnoredReason(kAXLabelFor, control_ax_object));
} }
return kIgnoreObject; return kIgnoreObject;
} }
...@@ -2811,7 +2814,7 @@ void AXNodeObject::SetNode(Node* node) { ...@@ -2811,7 +2814,7 @@ void AXNodeObject::SetNode(Node* node) {
node_ = node; node_ = node;
} }
AXObject* AXNodeObject::CorrespondingControlForLabelElement() const { AXObject* AXNodeObject::CorrespondingControlAXObjectForLabelElement() const {
HTMLLabelElement* label_element = LabelElementContainer(); HTMLLabelElement* label_element = LabelElementContainer();
if (!label_element) if (!label_element)
return nullptr; return nullptr;
...@@ -2829,6 +2832,14 @@ AXObject* AXNodeObject::CorrespondingControlForLabelElement() const { ...@@ -2829,6 +2832,14 @@ AXObject* AXNodeObject::CorrespondingControlForLabelElement() const {
return AXObjectCache().GetOrCreate(corresponding_control); return AXObjectCache().GetOrCreate(corresponding_control);
} }
AXObject* AXNodeObject::CorrespondingLabelAXObject() const {
HTMLLabelElement* label_element = LabelElementContainer();
if (!label_element)
return nullptr;
return AXObjectCache().GetOrCreate(label_element);
}
HTMLLabelElement* AXNodeObject::LabelElementContainer() const { HTMLLabelElement* AXNodeObject::LabelElementContainer() const {
if (!GetNode()) if (!GetNode())
return nullptr; return nullptr;
......
...@@ -79,7 +79,8 @@ class MODULES_EXPORT AXNodeObject : public AXObject { ...@@ -79,7 +79,8 @@ class MODULES_EXPORT AXNodeObject : public AXObject {
Element* MouseButtonListener() const; Element* MouseButtonListener() const;
bool IsNativeCheckboxOrRadio() const; bool IsNativeCheckboxOrRadio() const;
void SetNode(Node*); void SetNode(Node*);
AXObject* CorrespondingControlForLabelElement() const; AXObject* CorrespondingControlAXObjectForLabelElement() const;
AXObject* CorrespondingLabelAXObject() const;
HTMLLabelElement* LabelElementContainer() const; HTMLLabelElement* LabelElementContainer() const;
// //
......
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