Bug 1687681: Add France specific CC expiry date transformation. r=dimi,sfoster

Our previous regex did not parse "AAAA" or "AA" so I've added this to the transformation regex.
For the year first regex, I added a separate regex because trying to bolt on the missing "AA" caused the resulting parsed result to drop characters for other locales ("yyyy" became "yyy" and the same with "aaaa").

Remove backslash as a supported month/year divider when checking expiry date placeholders.

Differential Revision: https://phabricator.services.mozilla.com/D120550
This commit is contained in:
Tim Giles 2021-08-17 15:58:48 +00:00
Родитель 383665d07c
Коммит 9285e090f8
6 изменённых файлов: 357 добавлений и 19 удалений

Просмотреть файл

@ -0,0 +1,135 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Checkout Payment - Ebay - FR</title>
</head>
<body class="xo desktop">
<div id="root">
<div class="bodyContent" tabindex="-1">
<div userid="hadox-45" isebayintermediated="true" aria-hidden="true"></div>
<div id="gh-gb" tabindex="-1"></div>
<h1 class="page-title">Finalisation de l'achat</h1>
<div class="credit-card-container">
<div class="form-element card-number">
<div class="credit-card-number" aria-live="polite">
<div class="float-label expanded"><label for="cardNumber">Numéro de carte</label>
<div><input aria-required="true" data-validations="REQUIRED_FIELD" class="" autocomplete="cc-number"
data-val_required_field_params="{}" id="cardNumber" type="tel" name="cardNumber"
value="4111 1111 1111 1111" error=""></div>
<div class="card-types" aria-live="polite"><span aria-hidden="false" class="payment-logo VISA small"
aria-label="VISA" role="img"></span></div>
</div>
</div>
</div>
<div class="">
<div class="form-element">
<div class="float-label expanded"><label for="cardExpiryDate">Date d'expiration</label>
<div><input autocomplete="cc-exp" class="" aria-describedby="cardExpiryDate-accessorylabel"
aria-required="true" data-val_month_and_year_format_params="{}" data-val_required_field_params="{}"
id="cardExpiryDate" type="tel" name="cardExpiryDate" placeholder="MM / AA"
data-validations="MONTH_AND_YEAR_FORMAT,REQUIRED_FIELD" value="02/23" error=""></div>
<div id="cardExpiryDate-accessorylabel" class="secondary-text"></div>
</div>
</div>
<div class="form-element card-cvv">
<div class="float-label"><label for="securityCode">Code de sécurité</label>
<div><input autocomplete="cc-csc" maxlength="3" optionaltext="Facultatif pour une carte de débit."
pattern="[0-9]*" id="securityCode" type="tel" name="securityCode" class=""
placeholder="3 ou 4 chiffres" data-validations="CVV_NUMBER" value="" cardnumber="4111 1111 1111 1111"
data-val_cvv_number_params="{}"></div>
<div class="bubble"><span class="bubble-child"><span class="bubblehelp"><span class="infotip "><button
tabindex="0" aria-expanded="false" aria-label="En savoir plus sur le code de sécurité."
class="icon-btn infotip__host" type="button"><svg height="24px" width="24px"
class="icon icon--information " xmlns="http://www.w3.org/2000/svg" focusable="false"
aria-hidden="true">
<use xlink:href="#icon-information"></use>
</svg></button><span class="infotip__overlay" role="tooltip"
style="inset: 218.3px auto auto 709.7px; position: fixed;"><span
class="infotip__pointer infotip__pointer--top-left"></span><span class="infotip__mask"><span
class="infotip__cell"><span class="infotip__content">
<div class="ICON"><span class="loadable-icon-and-text"><span
class="visa-cvv loadable-icon-and-text-icon" aria-hidden="true"></span></span></div>
<div class="ICON"><span class="loadable-icon-and-text"><span
class="amex-cvv loadable-icon-and-text-icon" aria-hidden="true"></span></span></div>
<div class="TITLE"><span class="loadable-icon-and-text"><span class="text-display"><span
class="">Visa, Mastercard ou Discover</span></span></span></div>
<ul>
<li class="DETAILS_LIST"><span class="loadable-icon-and-text"><span
class="text-display"><span class="">Il s'agit du numéro à 3&nbsp;chiffres situé au
verso de votre carte, à côté de l'emplacement réservé à la
signature.</span></span></span></li>
</ul>
<div class="TITLE"><span class="loadable-icon-and-text"><span class="text-display"><span
class="">American Express</span></span></span></div>
<ul>
<li class="DETAILS_LIST"><span class="loadable-icon-and-text"><span
class="text-display"><span class="">Il s'agit du numéro à 4&nbsp;chiffres situé au
recto de votre carte au-dessus de son numéro.</span></span></span></li>
</ul>
</span><button aria-label="Close CVV Information Overlay" class="infotip__close"
type="button"><svg height="24px" width="24px" class="icon icon--close "
xmlns="http://www.w3.org/2000/svg" focusable="false" aria-hidden="true">
<use xlink:href="#icon-close"></use>
</svg></button></span></span></span></span></span></span></div>
</div>
</div>
</div>
<div class="form-element">
<div class="float-label expanded"><label for="cardHolderFirstName">Prénom</label>
<div><input autocomplete="cc-given-name" aria-required="true"
aria-describedby="cardHolderFirstName-accessorylabel" data-val_required_field_params="{}"
id="cardHolderFirstName" type="text" name="cardHolderFirstName" class=""
placeholder="Saisissez le prénom sur la carte" data-validations="REQUIRED_FIELD" value="John"></div>
<div id="cardHolderFirstName-accessorylabel" class="secondary-text"></div>
</div>
</div>
<div class="form-element">
<div class="float-label expanded"><label for="cardHolderLastName">Nom</label>
<div><input autocomplete="cc-family-name" aria-required="true"
aria-describedby="cardHolderLastName-accessorylabel" data-val_required_field_params="{}"
id="cardHolderLastName" type="text" name="cardHolderLastName" class=""
placeholder="Saisissez le nom sur la carte" data-validations="REQUIRED_FIELD" value="Smith"></div>
<div id="cardHolderLastName-accessorylabel" class="secondary-text"></div>
</div>
</div>
<div class="form-element remember-card"><span class="checkbox-wrapper field"><span
class="checkbox field__control"><svg style="display: none;">
<symbol id="icon-checkbox-checked" viewBox="0 0 22 22">
<path fill-rule="evenodd"
d="M1 0h20a1 1 0 011 1v20a1 1 0 01-1 1H1a1 1 0 01-1-1V1a1 1 0 011-1zm7.3 15.71a1 1 0 001.41 0l8-8h-.01a1 1 0 00-1.41-1.41L9 13.59 5.71 10.3a1 1 0 00-1.41 1.41l4 4z">
</path>
</symbol>
<symbol id="icon-checkbox-checked-small" viewBox="0 0 14 14">
<path
d="M13 0H1a1 1 0 00-1 1v12a1 1 0 001 1h12a1 1 0 001-1V1a1 1 0 00-1-1zm-2.29 5.71l-4 4a1 1 0 01-1.41 0l-2-2a1 1 0 011.41-1.42L6 7.59 9.29 4.3a1 1 0 011.41 1.41h.01z">
</path>
</symbol>
<symbol id="icon-checkbox-unchecked" viewBox="0 0 21 22">
<path fill-rule="evenodd"
d="M.955 0h19.09c.528 0 .955.448.955 1v20c0 .552-.427 1-.955 1H.955C.427 22 0 21.552 0 21V1c0-.552.427-1 .955-1zm.954 20h17.182V2H1.909v18z">
</path>
</symbol>
<symbol id="icon-checkbox-unchecked-small" viewBox="0 0 14 14">
<path d="M13 14H1a1 1 0 01-1-1V1a1 1 0 011-1h12a1 1 0 011 1v12a1 1 0 01-1 1zM2 12h10V2H2v10z"></path>
</symbol>
</svg><input for="rememberCard" id="rememberCard" class="checkbox__control " type="checkbox"
checked=""><span class="checkbox__icon" hidden=""><svg height="24px" width="24px"
class="checkbox__checked" xmlns="http://www.w3.org/2000/svg" focusable="false" aria-hidden="true">
<use xlink:href="#icon-checkbox-checked"></use>
</svg><svg height="24px" width="24px" class="checkbox__unchecked" xmlns="http://www.w3.org/2000/svg"
focusable="false" aria-hidden="true">
<use xlink:href="#icon-checkbox-unchecked"></use>
</svg></span></span><label class="field__label field__label--end" for="rememberCard"><span
class="text-display"><span class="">Enregistrer cette carte pour de futurs
achats</span></span></label></span></div>
</div>
</div>
</div>
</body>
</html>

Просмотреть файл

@ -0,0 +1,42 @@
/* global runHeuristicsTest */
"use strict";
runHeuristicsTest(
[
{
fixturePath: "Checkout_Payment_FR.html",
expectedResult: [
[
[
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-number",
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-exp"
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-given-name"
},
{
section: "",
addressType: "",
contactType: "",
fieldName: "cc-family-name"
},
],
],
],
},
],
"../../../fixtures/third_party/Ebay/"
)

Просмотреть файл

@ -970,6 +970,13 @@ const TESTCASES = [
],
expectedOptionElements: [],
},
{
description: "Fill a cc-exp without placeholder on the cc-exp field",
document: `<form><input autocomplete="cc-number">
<input autocomplete="cc-exp"></form>`,
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
expectedResult: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
},
{
description: "Use placeholder to adjust cc-exp format [mm/yy].",
document: `<form><input autocomplete="cc-number">
@ -1036,17 +1043,6 @@ const TESTCASES = [
}),
],
},
{
description: "Use placeholder to adjust cc-exp format [yyy-mm].",
document: `<form><input autocomplete="cc-number">
<input placeholder="yyy-mm" autocomplete="cc-exp"></form>`,
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
expectedResult: [
Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
"cc-exp": "025-01",
}),
],
},
{
description: "Use placeholder to adjust cc-exp format [mmm yyyy].",
document: `<form><input autocomplete="cc-number">

Просмотреть файл

@ -0,0 +1,138 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/*
* Test to ensure locale specific placeholders for credit card fields are properly used
* to transform various values in the profile.
*/
"use strict";
let FormAutofillHandler;
add_task(async function() {
({ FormAutofillHandler } = ChromeUtils.import(
"resource://autofill/FormAutofillHandler.jsm"
));
});
const DEFAULT_CREDITCARD_RECORD = {
guid: "123",
"cc-exp-month": 1,
"cc-exp-year": 2025,
"cc-exp": "2025-01",
};
const FR_TESTCASES = [
{
description: "Use placeholder to adjust cc-exp format [mm/aa].",
document: `<form><input autocomplete="cc-number">
<input placeholder="mm/aa" autocomplete="cc-exp"></form>`,
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
expectedResult: [
Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
"cc-exp": "01/25",
}),
],
},
{
description: "Use placeholder to adjust cc-exp format [mm / aa].",
document: `<form><input autocomplete="cc-number">
<input placeholder="mm / aa" autocomplete="cc-exp"></form>`,
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
expectedResult: [
Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
"cc-exp": "01/25",
}),
],
},
{
description: "Use placeholder to adjust cc-exp format [MM / AA].",
document: `<form><input autocomplete="cc-number">
<input placeholder="MM / AA" autocomplete="cc-exp"></form>`,
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
expectedResult: [
Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
"cc-exp": "01/25",
}),
],
},
{
description: "Use placeholder to adjust cc-exp format [mm / aaaa].",
document: `<form><input autocomplete="cc-number">
<input placeholder="mm / aaaa" autocomplete="cc-exp"></form>`,
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
expectedResult: [
Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
"cc-exp": "01/2025",
}),
],
},
{
description: "Use placeholder to adjust cc-exp format [mm - aaaa].",
document: `<form><input autocomplete="cc-number">
<input placeholder="mm - aaaa" autocomplete="cc-exp"></form>`,
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
expectedResult: [
Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
"cc-exp": "01-2025",
}),
],
},
{
description: "Use placeholder to adjust cc-exp format [aaaa-mm].",
document: `<form><input autocomplete="cc-number">
<input placeholder="aaaa-mm" autocomplete="cc-exp"></form>`,
profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
expectedResult: [
Object.assign({}, DEFAULT_CREDITCARD_RECORD, {
"cc-exp": "2025-01",
}),
],
},
];
const TESTCASES = [FR_TESTCASES];
for (let localeTests of TESTCASES) {
for (let testcase of localeTests) {
add_task(async function() {
info("Starting testcase: " + testcase.description);
let doc = MockDocument.createTestDocument(
"http://localhost:8080/test/",
testcase.document
);
let form = doc.querySelector("form");
let formLike = FormLikeFactory.createFromForm(form);
let handler = new FormAutofillHandler(formLike);
handler.collectFormFields();
handler.focusedInput = form.elements[0];
let adaptedRecords = handler.activeSection.getAdaptedProfiles(
testcase.profileData
);
Assert.deepEqual(adaptedRecords, testcase.expectedResult);
if (testcase.expectedOptionElements) {
testcase.expectedOptionElements.forEach((expectedOptionElement, i) => {
for (let field in expectedOptionElement) {
let select = form.querySelector(`[autocomplete=${field}]`);
let expectedOption = doc.getElementById(
expectedOptionElement[field]
);
Assert.notEqual(expectedOption, null);
let value = testcase.profileData[i][field];
let cache = handler.activeSection._cacheValue.matchingSelectOption.get(
select
);
let targetOption = cache[value] && cache[value].get();
Assert.notEqual(targetOption, null);
Assert.equal(targetOption, expectedOption);
}
});
}
});
}
}

Просмотреть файл

@ -22,6 +22,7 @@ skip-if =
[test_extractLabelStrings.js]
[test_findLabelElements.js]
[test_getAdaptedProfiles.js]
[test_getAdaptedProfiles_locales.js]
[test_getCategoriesFromFieldNames.js]
[test_getFormInputDetails.js]
[test_getInfo.js]

Просмотреть файл

@ -1040,25 +1040,51 @@ class FormAutofillCreditCardSection extends FormAutofillSection {
ccExpYear = profile["cc-exp-year"],
placeholder = element.placeholder;
result = /(?:[^m]|\b)(m{1,2})\s*([-/\\]*)\s*(y{2,4})(?!y)/i.exec(
placeholder
// Bug 1687681: This is a short term fix to other locales having
// different characters to represent year.
// For example, FR locales may use "A" instead of "Y" to represent year
// This approach will not scale well and should be investigated in a follow up bug.
let monthChars = "m";
let yearChars = "ya";
let monthFirstCheck = new RegExp(
"(?:\\b|^)((?:[" +
monthChars +
"]{2}){1,2})\\s*([\\-/])\\s*((?:[" +
yearChars +
"]{2}){1,2})(?:\\b|$)",
"i"
);
// If the month first check finds a result, where placeholder is "mm - yyyy",
// the result will be structured as such: ["mm - yyyy", "mm", "-", "yyyy"]
result = monthFirstCheck.exec(placeholder);
if (result) {
profile["cc-exp"] =
String(ccExpMonth).padStart(result[1].length, "0") +
ccExpMonth.toString().padStart(result[1].length, "0") +
result[2] +
String(ccExpYear).substr(-1 * result[3].length);
ccExpYear.toString().substr(-1 * result[3].length);
return;
}
result = /(?:[^y]|\b)(y{2,4})\s*([-/\\]*)\s*(m{1,2})(?!m)/i.exec(
placeholder
let yearFirstCheck = new RegExp(
"(?:\\b|^)((?:[" +
yearChars +
"]{2}){1,2})\\s*([\\-/])\\s*((?:[" + // either one or two counts of 'yy' or 'aa' sequence
monthChars +
"]){1,2})(?:\\b|$)",
"i" // either one or two counts of a 'm' sequence
);
// If the year first check finds a result, where placeholder is "yyyy mm",
// the result will be structured as such: ["yyyy mm", "yyyy", " ", "mm"]
result = yearFirstCheck.exec(placeholder);
if (result) {
profile["cc-exp"] =
String(ccExpYear).substr(-1 * result[1].length) +
ccExpYear.toString().substr(-1 * result[1].length) +
result[2] +
String(ccExpMonth).padStart(result[3].length, "0");
ccExpMonth.toString().padStart(result[3].length, "0");
}
}