Commit 95c8ebc6 authored by Roger McFarlane's avatar Roger McFarlane Committed by Commit Bot

[autofill] add iOS helpers to get aria-label and aria-description

This CL adds the getAriaLabel() and getAriaDescription() helper
functions to the __GCrWeb.fill "namespace". These functions return
the ARIA label and description strings associated with an element.

The ARIA Label is the text referenced by one or more containing element
ids in the labelled elements aria-labelledby attribute, or the value
of the element's aria-label attribute, with preference given to the
aria-labelledby attribute. For example:

    <div id='foo-label'>Label for Foo</div>
    <input name='foo' aria-labelledby='foo-label'>
    <input name='bar' aria-label='Label for Bar'>

The aria-description is similar to aria-labelledby. For example:

    <div id='foo-description'>Long descriptive text for Foo</div>
    <input name='foo' aria-describedby='foo-descrition'>

Bug: 896719
Change-Id: I300b84298fc20e1b86474f60d08cdd1c9569036b
Reviewed-on: https://chromium-review.googlesource.com/c/1325032Reviewed-by: default avatarSebastien Seguin-Gagnon <sebsg@chromium.org>
Reviewed-by: default avatarMoe Ahmadi <mahmadi@chromium.org>
Commit-Queue: Roger McFarlane <rogerm@chromium.org>
Cr-Commit-Position: refs/heads/master@{#606534}
parent 97418a9f
...@@ -25,6 +25,8 @@ class FillJsTest : public web::WebJsTest<web::WebTestWithWebState> { ...@@ -25,6 +25,8 @@ class FillJsTest : public web::WebJsTest<web::WebTestWithWebState> {
@[ @"chrome_bundle_all_frames" ]) {} @[ @"chrome_bundle_all_frames" ]) {}
}; };
} // namespace
TEST_F(FillJsTest, GetCanonicalActionForForm) { TEST_F(FillJsTest, GetCanonicalActionForForm) {
struct TestData { struct TestData {
NSString* html_action; NSString* html_action;
...@@ -69,4 +71,126 @@ TEST_F(FillJsTest, GetCanonicalActionForForm) { ...@@ -69,4 +71,126 @@ TEST_F(FillJsTest, GetCanonicalActionForForm) {
} }
} }
} // namespace // Tests the extraction of the aria-label attribute.
TEST_F(FillJsTest, GetAriaLabel) {
LoadHtmlAndInject(@"<input id='input' type='text' aria-label='the label'/>");
id result = ExecuteJavaScript(
@"__gCrWeb.fill.getAriaLabel(document.getElementById('input'));");
NSString* expected_result = @"the label";
EXPECT_NSEQ(result, expected_result);
}
// Tests that aria-labelledby works. Simple case: only one id referenced.
TEST_F(FillJsTest, GetAriaLabelledBySingle) {
LoadHtmlAndInject(
@"<div id='billing'>Billing</div>"
@"<div>"
@" <div id='name'>Name</div>"
@" <input id='input' type='text' aria-labelledby='name'/>"
@"</div>");
id result = ExecuteJavaScript(
@"__gCrWeb.fill.getAriaLabel(document.getElementById('input'));");
NSString* expected_result = @"Name";
EXPECT_NSEQ(result, expected_result);
}
// Tests that aria-labelledby works: Complex case: multiple ids referenced.
TEST_F(FillJsTest, GetAriaLabelledByMulti) {
LoadHtmlAndInject(
@"<div id='billing'>Billing</div>"
@"<div>"
@" <div id='name'>Name</div>"
@" <input id='input' type='text' aria-labelledby='billing name'/>"
@"</div>");
id result = ExecuteJavaScript(
@"__gCrWeb.fill.getAriaLabel(document.getElementById('input'));");
NSString* expected_result = @"Billing Name";
EXPECT_NSEQ(result, expected_result);
}
// Tests that aria-labelledby takes precedence over aria-label
TEST_F(FillJsTest, GetAriaLabelledByTakesPrecedence) {
LoadHtmlAndInject(
@"<div id='billing'>Billing</div>"
@"<div>"
@" <div id='name'>Name</div>"
@" <input id='input' type='text' aria-label='ignored' "
@" aria-labelledby='name'/>"
@"</div>");
id result = ExecuteJavaScript(
@"__gCrWeb.fill.getAriaLabel(document.getElementById('input'));");
NSString* expected_result = @"Name";
EXPECT_NSEQ(result, expected_result);
}
// Tests that an invalid aria-labelledby reference gets ignored (as opposed to
// crashing, for example).
TEST_F(FillJsTest, GetAriaLabelledByInvalid) {
LoadHtmlAndInject(
@"<div id='billing'>Billing</div>"
@"<div>"
@" <div id='name'>Name</div>"
@" <input id='input' type='text' aria-labelledby='div1 div2'/>"
@"</div>");
id result = ExecuteJavaScript(
@"__gCrWeb.fill.getAriaLabel(document.getElementById('input'));");
NSString* expected_result = @"";
EXPECT_NSEQ(result, expected_result);
}
// Tests that invalid aria-labelledby references fall back to aria-label.
TEST_F(FillJsTest, GetAriaLabelledByFallback) {
LoadHtmlAndInject(
@"<div id='billing'>Billing</div>"
@"<div>"
@" <div id='name'>Name</div>"
@" <input id='input' type='text' aria-label='valid' "
@" aria-labelledby='div1 div2'/>"
@"</div>");
id result = ExecuteJavaScript(
@"__gCrWeb.fill.getAriaLabel(document.getElementById('input'));");
NSString* expected_result = @"valid";
EXPECT_NSEQ(result, expected_result);
}
// Tests that aria-describedby works: Simple case: a single id referenced.
TEST_F(FillJsTest, GetAriaDescriptionSingle) {
LoadHtmlAndInject(
@"<input id='input' type='text' aria-describedby='div1'/>"
@"<div id='div1'>aria description</div>");
id result = ExecuteJavaScript(
@"__gCrWeb.fill.getAriaDescription(document.getElementById('input'));");
NSString* expected_result = @"aria description";
EXPECT_NSEQ(result, expected_result);
}
// Tests that aria-describedby works: Complex case: multiple ids referenced.
TEST_F(FillJsTest, GetAriaDescriptionMulti) {
LoadHtmlAndInject(
@"<input id='input' type='text' aria-describedby='div1 div2'/>"
@"<div id='div2'>description</div>"
@"<div id='div1'>aria</div>");
id result = ExecuteJavaScript(
@"__gCrWeb.fill.getAriaDescription(document.getElementById('input'));");
NSString* expected_result = @"aria description";
EXPECT_NSEQ(result, expected_result);
}
// Tests that invalid aria-describedby returns the empty string.
TEST_F(FillJsTest, GetAriaDescriptionInvalid) {
LoadHtmlAndInject(
@"<input id='input' type='text' aria-describedby='invalid'/>");
id result = ExecuteJavaScript(
@"__gCrWeb.fill.getAriaDescription(document.getElementById('input'));");
NSString* expected_result = @"";
EXPECT_NSEQ(result, expected_result);
}
...@@ -1993,4 +1993,64 @@ __gCrWeb.fill.autofillSubmissionData = function(form) { ...@@ -1993,4 +1993,64 @@ __gCrWeb.fill.autofillSubmissionData = function(form) {
return __gCrWeb.stringify([formData]); return __gCrWeb.stringify([formData]);
} }
/**
* Returns the coalesced child text of the elements who's ids are found in
* the |atrtribute| of |element|.
*
* For example, given this document...
*
* <div id="billing">Billing</div>
* <div>
* <div id="name">Name</div>
* <input id="field1" type="text" aria-labelledby="billing name"/>
* </div>
* <div>
* <div id="address">Address</div>
* <input id="field2" type="text" aria-labelledby="billing address"/>
* </div>
*
* The coalesced text by the id_list found in the aria-labelledby attribute
* of the field1 input element would be "Billing Name" and for field2 it would
* be "Billing Address".
*/
function coalesceTextByIdList(element, attribute) {
if (!element) {
return '';
}
var ids = element.getAttribute(attribute);
if (!ids) {
return '';
}
return ids.trim()
.split(/\s+/)
.map(function(i) { return document.getElementById(i); })
.filter(function(e) { return e !== null; })
.map(function (n) { return __gCrWeb.fill.findChildText(n); })
.filter(function (s) { return s.length > 0; })
.join(' ')
.trim();
}
/**
* Returns the coalesced text referenced by the aria-labelledby attribute
* or the value of the aria-label attribute, with priority given to the
* aria-labelledby text.
*/
__gCrWeb.fill.getAriaLabel = function(element) {
var label = coalesceTextByIdList(element, 'aria-labelledby');
if (!label) {
label = element.getAttribute('aria-label') || '';
}
return label.trim();
}
/**
* Returns the coalesced text referenced by the aria-describedby attribute.
*/
__gCrWeb.fill.getAriaDescription = function(element) {
return coalesceTextByIdList(element, 'aria-describedby');
}
}()); // End of anonymous object }()); // End of anonymous object
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