Bug 1371145 - Calculate the possible option of cc-exp/cc-exp-year/cc-exp-month for filling and previewing. r=lchang draft
authorSean Lee <selee@mozilla.com>
Sun, 13 Aug 2017 22:37:13 +0800
changeset 648134 45e9c314647c3865b17a3c42630f44b839b71db9
parent 647413 6cc2059934062cecc38c5607f70c8df348f29d92
child 726715 b0d950069bf43f31c0f3802d6cb9aebb1cdc0787
push id74634
push userbmo:selee@mozilla.com
push dateThu, 17 Aug 2017 08:52:49 +0000
reviewerslchang
bugs1371145
milestone57.0a1
Bug 1371145 - Calculate the possible option of cc-exp/cc-exp-year/cc-exp-month for filling and previewing. r=lchang MozReview-Commit-ID: KKMkoDHKOvR
browser/extensions/formautofill/FormAutofillHandler.jsm
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/ProfileStorage.jsm
browser/extensions/formautofill/test/unit/test_creditCardRecords.js
browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
--- a/browser/extensions/formautofill/FormAutofillHandler.jsm
+++ b/browser/extensions/formautofill/FormAutofillHandler.jsm
@@ -296,17 +296,17 @@ FormAutofillHandler.prototype = {
         focusedInput.setUserInput(value);
       }
       this.creditCard.filledRecordGUID = profile.guid;
       fieldDetails = this.creditCard.fieldDetails;
     } else if (FormAutofillUtils.isAddressField(focusedDetail.fieldName)) {
       this.address.filledRecordGUID = profile.guid;
       fieldDetails = this.address.fieldDetails;
     } else {
-      throw "Unknown form fields";
+      throw new Error("Unknown form fields");
     }
 
     log.debug("profile in autofillFormFields:", profile);
 
     for (let fieldDetail of fieldDetails) {
       // Avoid filling field value in the following cases:
       // 1. the focused input which is filled in FormFillController.
       // 2. a non-empty input field
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -45,16 +45,17 @@ this.FormAutofillUtils = {
     "tel-local-prefix": "tel",
     "tel-local-suffix": "tel",
     "tel-extension": "tel",
     "email": "email",
     "cc-name": "creditCard",
     "cc-number": "creditCard",
     "cc-exp-month": "creditCard",
     "cc-exp-year": "creditCard",
+    "cc-exp": "creditCard",
   },
   _addressDataLoaded: false,
   _collators: {},
   _reAlternativeCountryNames: {},
 
   isAddressField(fieldName) {
     return !!this._fieldNameInfo[fieldName] && !this.isCreditCardField(fieldName);
   },
@@ -275,28 +276,38 @@ this.FormAutofillUtils = {
           return country;
         }
       }
     }
 
     return null;
   },
 
+  findSelectOption(selectEl, record, fieldName) {
+    if (this.isAddressField(fieldName)) {
+      return this.findAddressSelectOption(selectEl, record, fieldName);
+    }
+    if (this.isCreditCardField(fieldName)) {
+      return this.findCreditCardSelectOption(selectEl, record, fieldName);
+    }
+    return null;
+  },
+
   /**
    * Find the option element from select element.
    * 1. Try to find the locale using the country from address.
    * 2. First pass try to find exact match.
    * 3. Second pass try to identify values from address value and options,
    *    and look for a match.
    * @param   {DOMElement} selectEl
    * @param   {object} address
    * @param   {string} fieldName
    * @returns {DOMElement}
    */
-  findSelectOption(selectEl, address, fieldName) {
+  findAddressSelectOption(selectEl, address, fieldName) {
     let value = address[fieldName];
     if (!value) {
       return null;
     }
 
     let country = address.country || this.DEFAULT_COUNTRY_CODE;
     let dataset = this.getCountryAddressData(country);
     let collators = this.getCollators(country);
@@ -347,16 +358,74 @@ this.FormAutofillUtils = {
         }
         break;
       }
     }
 
     return null;
   },
 
+  findCreditCardSelectOption(selectEl, creditCard, fieldName) {
+    let expMonthInt = parseInt(creditCard["cc-exp-month"], 10);
+    let oneDigitMonth = expMonthInt.toString();
+    let twoDigitsMonth = oneDigitMonth.length < 2 ? "0" + oneDigitMonth : oneDigitMonth;
+    let expYearInt = parseInt(creditCard["cc-exp-year"], 10);
+    let twoDigitsYear = (expYearInt - 2000).toString();
+    let fourDigitsYear = expYearInt.toString();
+    let options = Array.from(selectEl.options);
+
+    switch (fieldName) {
+      case "cc-exp-month": {
+        for (let option of options) {
+          if ([option.text, option.label, option.value].some(
+            s => parseInt(s, 10) == expMonthInt
+          )) {
+            return option;
+          }
+        }
+        break;
+      }
+      case "cc-exp-year": {
+        for (let option of options) {
+          if ([option.text, option.label, option.value].some(
+            s => s == twoDigitsYear || s == fourDigitsYear
+          )) {
+            return option;
+          }
+        }
+        break;
+      }
+      case "cc-exp": {
+        let patterns = [
+          oneDigitMonth + "/" + twoDigitsYear,
+          oneDigitMonth + "/" + fourDigitsYear,
+          twoDigitsMonth + "/" + twoDigitsYear,
+          twoDigitsMonth + "/" + fourDigitsYear,
+          oneDigitMonth + "-" + twoDigitsYear,
+          oneDigitMonth + "-" + fourDigitsYear,
+          twoDigitsMonth + "-" + twoDigitsYear,
+          twoDigitsMonth + "-" + fourDigitsYear,
+          twoDigitsYear + "-" + twoDigitsMonth,
+          fourDigitsYear + "-" + twoDigitsMonth,
+        ];
+
+        for (let option of options) {
+          if ([option.text, option.label, option.value].some(
+            str => patterns.some(pattern => pattern == str)
+          )) {
+            return option;
+          }
+        }
+        break;
+      }
+    }
+
+    return null;
+  },
+
   /**
    * Try to match value with keys and names, but always return the key.
    * @param   {array<string>} keys
    * @param   {array<string>} names
    * @param   {string} value
    * @param   {array} collators
    * @returns {string}
    */
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -1462,16 +1462,21 @@ class CreditCards extends AutofillRecord
     if (!("cc-given-name" in creditCard)) {
       let nameParts = FormAutofillNameUtils.splitName(creditCard["cc-name"]);
       creditCard["cc-given-name"] = nameParts.given;
       creditCard["cc-additional-name"] = nameParts.middle;
       creditCard["cc-family-name"] = nameParts.family;
       hasNewComputedFields = true;
     }
 
+    if (!("cc-exp" in creditCard) && "cc-exp-month" in creditCard && "cc-exp-year" in creditCard) {
+      creditCard["cc-exp"] = creditCard["cc-exp-month"] + "/" + creditCard["cc-exp-year"];
+      hasNewComputedFields = true;
+    }
+
     return hasNewComputedFields;
   }
 
   _normalizeFields(creditCard) {
     // Fields that should not be set by content.
     delete creditCard["cc-number-encrypted"];
 
     // Validate and encrypt credit card numbers, and calculate the masked numbers
--- a/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -244,20 +244,23 @@ add_task(async function test_validate() 
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
   profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS);
 
   let creditCards = profileStorage.creditCards.getAll();
 
   do_check_eq(creditCards[0]["cc-exp-month"], undefined);
   do_check_eq(creditCards[0]["cc-exp-year"], undefined);
+  do_check_eq(creditCards[0]["cc-exp"], undefined);
 
-  do_check_eq(creditCards[1]["cc-exp-month"], TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-month"]);
-  do_check_eq(creditCards[1]["cc-exp-year"],
-    parseInt(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-year"], 10) + 2000);
+  let month = TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-month"];
+  let year = parseInt(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-year"], 10) + 2000;
+  do_check_eq(creditCards[1]["cc-exp-month"], month);
+  do_check_eq(creditCards[1]["cc-exp-year"], year);
+  do_check_eq(creditCards[1]["cc-exp"], month + "/" + year);
 
   do_check_eq(creditCards[2]["cc-number"].length, 16);
   // TODO: Check the decrypted numbers should not contain spaces after
   //       decryption lands (bug 1337314).
 
   Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_NUMBERS),
     /Credit card number contains invalid characters\./);
 });
--- a/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
+++ b/browser/extensions/formautofill/test/unit/test_getAdaptedProfiles.js
@@ -1,269 +1,463 @@
 /*
  * Test for form auto fill content helper fill all inputs function.
  */
 
 "use strict";
 
 Cu.import("resource://formautofill/FormAutofillHandler.jsm");
 
-const DEFAULT_PROFILE = {
+const DEFAULT_ADDRESS_RECORD = {
   "guid": "123",
   "street-address": "2 Harrison St\nline2\nline3",
   "address-line1": "2 Harrison St",
   "address-line2": "line2",
   "address-line3": "line3",
   "address-level1": "CA",
   "country": "US",
 };
 
+const DEFAULT_CREDITCARD_RECORD = {
+  "guid": "123",
+  "cc-exp-month": "1",
+  "cc-exp-year": "2025",
+  "cc-exp": "1/2025",
+};
+
 const TESTCASES = [
   {
-    description: "Form with street-address",
+    description: "Address form with street-address",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
   },
   {
-    description: "Form with street-address, address-line[1, 2, 3]",
+    description: "Address form with street-address, address-line[1, 2, 3]",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                <input id="line1" autocomplete="address-line1">
                <input id="line2" autocomplete="address-line2">
                <input id="line3" autocomplete="address-line3">
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
   },
   {
-    description: "Form with street-address, address-line1",
+    description: "Address form with street-address, address-line1",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                <input id="line1" autocomplete="address-line1">
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St line2 line3",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
   },
   {
-    description: "Form with street-address, address-line[1, 2]",
+    description: "Address form with street-address, address-line[1, 2]",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                <input id="line1" autocomplete="address-line1">
                <input id="line2" autocomplete="address-line2">
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2 line3",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
   },
   {
-    description: "Form with street-address, address-line[1, 3]",
+    description: "Address form with street-address, address-line[1, 3]",
     document: `<form>
                <input id="street-addr" autocomplete="street-address">
                <input id="line1" autocomplete="address-line1">
                <input id="line3" autocomplete="address-line3">
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St line2 line3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2 line3",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
   },
   {
-    description: "Form with exact matching options in select",
+    description: "Address form with exact matching options in select",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-XX" value="XX">Dummy</option>
                  <option id="option-address-level1-CA" value="CA">California</option>
                </select>
                <select autocomplete="country">
                  <option id="option-country-XX" value="XX">Dummy</option>
                  <option id="option-country-US" value="US">United States</option>
                </select>
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
     expectedOptionElements: [{
       "address-level1": "option-address-level1-CA",
       "country": "option-country-US",
     }],
   },
   {
-    description: "Form with inexact matching options in select",
+    description: "Address form with inexact matching options in select",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-XX" value="XX">Dummy</option>
                  <option id="option-address-level1-OO" value="OO">California</option>
                </select>
                <select autocomplete="country">
                  <option id="option-country-XX" value="XX">Dummy</option>
                  <option id="option-country-OO" value="OO">United States</option>
                </select>
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
     expectedOptionElements: [{
       "address-level1": "option-address-level1-OO",
       "country": "option-country-OO",
     }],
   },
   {
-    description: "Form with value-omitted options in select",
+    description: "Address form with value-omitted options in select",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-1" value="">Dummy</option>
                  <option id="option-address-level1-2" value="">California</option>
                </select>
                <select autocomplete="country">
                  <option id="option-country-1" value="">Dummy</option>
                  <option id="option-country-2" value="">United States</option>
                </select>
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
     expectedOptionElements: [{
       "address-level1": "option-address-level1-2",
       "country": "option-country-2",
     }],
   },
   {
-    description: "Form with options with the same value in select",
+    description: "Address form with options with the same value in select ",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-same1" value="same">Dummy</option>
                  <option id="option-address-level1-same2" value="same">California</option>
                </select>
                <select autocomplete="country">
                  <option id="option-country-same1" value="sametoo">Dummy</option>
                  <option id="option-country-same2" value="sametoo">United States</option>
                </select>
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
       "address-level1": "CA",
       "country": "US",
     }],
     expectedOptionElements: [{
       "address-level1": "option-address-level1-same2",
       "country": "option-country-same2",
     }],
   },
   {
-    description: "Form without matching options in select",
+    description: "Address form without matching options in select for address-level1 and country",
     document: `<form>
                <select autocomplete="address-level1">
                  <option id="option-address-level1-dummy1" value="">Dummy</option>
                  <option id="option-address-level1-dummy2" value="">Dummy 2</option>
                </select>
                <select autocomplete="country">
                  <option id="option-country-dummy1" value="">Dummy</option>
                  <option id="option-country-dummy2" value="">Dummy 2</option>
                </select>
                </form>`,
-    profileData: [Object.assign({}, DEFAULT_PROFILE)],
+    profileData: [Object.assign({}, DEFAULT_ADDRESS_RECORD)],
     expectedResult: [{
       "guid": "123",
       "street-address": "2 Harrison St\nline2\nline3",
       "-moz-street-address-one-line": "2 Harrison St line2 line3",
       "address-line1": "2 Harrison St",
       "address-line2": "line2",
       "address-line3": "line3",
     }],
   },
+  {
+    description: "Credit Card form with matching options of cc-exp-year and cc-exp-month",
+    document: `<form>
+               <select autocomplete="cc-exp-month">
+                 <option id="option-cc-exp-month-01" value="1">01</option>
+                 <option id="option-cc-exp-month-02" value="2">02</option>
+                 <option id="option-cc-exp-month-03" value="3">03</option>
+                 <option id="option-cc-exp-month-04" value="4">04</option>
+                 <option id="option-cc-exp-month-05" value="5">05</option>
+                 <option id="option-cc-exp-month-06" value="6">06</option>
+                 <option id="option-cc-exp-month-07" value="7">07</option>
+                 <option id="option-cc-exp-month-08" value="8">08</option>
+                 <option id="option-cc-exp-month-09" value="9">09</option>
+                 <option id="option-cc-exp-month-10" value="10">10</option>
+                 <option id="option-cc-exp-month-11" value="11">11</option>
+                 <option id="option-cc-exp-month-12" value="12">12</option>
+               </select>
+               <select autocomplete="cc-exp-year">
+                 <option id="option-cc-exp-year-17" value="2017">17</option>
+                 <option id="option-cc-exp-year-18" value="2018">18</option>
+                 <option id="option-cc-exp-year-19" value="2019">19</option>
+                 <option id="option-cc-exp-year-20" value="2020">20</option>
+                 <option id="option-cc-exp-year-21" value="2021">21</option>
+                 <option id="option-cc-exp-year-22" value="2022">22</option>
+                 <option id="option-cc-exp-year-23" value="2023">23</option>
+                 <option id="option-cc-exp-year-24" value="2024">24</option>
+                 <option id="option-cc-exp-year-25" value="2025">25</option>
+                 <option id="option-cc-exp-year-26" value="2026">26</option>
+                 <option id="option-cc-exp-year-27" value="2027">27</option>
+                 <option id="option-cc-exp-year-28" value="2028">28</option>
+               </select>
+               </form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{
+      "cc-exp-month": "option-cc-exp-month-01",
+      "cc-exp-year": "option-cc-exp-year-25",
+    }],
+  },
+  {
+    description: "Credit Card form with matching options which contain labels",
+    document: `<form>
+               <select autocomplete="cc-exp-month">
+                 <option value="" selected="selected">Month</option>
+                 <option label="01 - January" id="option-cc-exp-month-01" value="object:17">dummy</option>
+                 <option label="02 - February" id="option-cc-exp-month-02" value="object:18">dummy</option>
+                 <option label="03 - March" id="option-cc-exp-month-03" value="object:19">dummy</option>
+                 <option label="04 - April" id="option-cc-exp-month-04" value="object:20">dummy</option>
+                 <option label="05 - May" id="option-cc-exp-month-05" value="object:21">dummy</option>
+                 <option label="06 - June" id="option-cc-exp-month-06" value="object:22">dummy</option>
+                 <option label="07 - July" id="option-cc-exp-month-07" value="object:23">dummy</option>
+                 <option label="08 - August" id="option-cc-exp-month-08" value="object:24">dummy</option>
+                 <option label="09 - September" id="option-cc-exp-month-09" value="object:25">dummy</option>
+                 <option label="10 - October" id="option-cc-exp-month-10" value="object:26">dummy</option>
+                 <option label="11 - November" id="option-cc-exp-month-11" value="object:27">dummy</option>
+                 <option label="12 - December" id="option-cc-exp-month-12" value="object:28">dummy</option>
+               </select>
+               <select autocomplete="cc-exp-year">
+                 <option value="" selected="selected">Year</option>
+                 <option label="2017" id="option-cc-exp-year-17" value="object:29">dummy</option>
+                 <option label="2018" id="option-cc-exp-year-18" value="object:30">dummy</option>
+                 <option label="2019" id="option-cc-exp-year-19" value="object:31">dummy</option>
+                 <option label="2020" id="option-cc-exp-year-20" value="object:32">dummy</option>
+                 <option label="2021" id="option-cc-exp-year-21" value="object:33">dummy</option>
+                 <option label="2022" id="option-cc-exp-year-22" value="object:34">dummy</option>
+                 <option label="2023" id="option-cc-exp-year-23" value="object:35">dummy</option>
+                 <option label="2024" id="option-cc-exp-year-24" value="object:36">dummy</option>
+                 <option label="2025" id="option-cc-exp-year-25" value="object:37">dummy</option>
+                 <option label="2026" id="option-cc-exp-year-26" value="object:38">dummy</option>
+                 <option label="2027" id="option-cc-exp-year-27" value="object:39">dummy</option>
+                 <option label="2028" id="option-cc-exp-year-28" value="object:40">dummy</option>
+                 <option label="2029" id="option-cc-exp-year-29" value="object:41">dummy</option>
+                 <option label="2030" id="option-cc-exp-year-30" value="object:42">dummy</option>
+                 <option label="2031" id="option-cc-exp-year-31" value="object:43">dummy</option>
+                 <option label="2032" id="option-cc-exp-year-32" value="object:44">dummy</option>
+                 <option label="2033" id="option-cc-exp-year-33" value="object:45">dummy</option>
+                 <option label="2034" id="option-cc-exp-year-34" value="object:46">dummy</option>
+                 <option label="2035" id="option-cc-exp-year-35" value="object:47">dummy</option>
+               </select>
+               </form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{
+      "cc-exp-month": "option-cc-exp-month-01",
+      "cc-exp-year": "option-cc-exp-year-25",
+    }],
+  },
+  {
+    description: "Compound cc-exp: {MON1}/{YEAR2}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="3/17">3/17</option>
+                 <option value="1/25" id="selected-cc-exp">1/25</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
+  {
+    description: "Compound cc-exp: {MON1}/{YEAR4}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="3/2017">3/2017</option>
+                 <option value="1/2025" id="selected-cc-exp">1/2025</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
+  {
+    description: "Compound cc-exp: {MON2}/{YEAR2}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="03/17">03/17</option>
+                 <option value="01/25" id="selected-cc-exp">01/25</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
+  {
+    description: "Compound cc-exp: {MON2}/{YEAR4}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="03/2017">03/2017</option>
+                 <option value="01/2025" id="selected-cc-exp">01/2025</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
+  {
+    description: "Compound cc-exp: {MON1}-{YEAR2}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="3-17">3-17</option>
+                 <option value="1-25" id="selected-cc-exp">1-25</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
+  {
+    description: "Compound cc-exp: {MON1}-{YEAR4}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="3-2017">3-2017</option>
+                 <option value="1-2025" id="selected-cc-exp">1-2025</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
+  {
+    description: "Compound cc-exp: {MON2}-{YEAR2}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="03-17">03-17</option>
+                 <option value="01-25" id="selected-cc-exp">01-25</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
+  {
+    description: "Compound cc-exp: {MON2}-{YEAR4}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="03-2017">03-2017</option>
+                 <option value="01-2025" id="selected-cc-exp">01-2025</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
+  {
+    description: "Compound cc-exp: {YEAR2}-{MON2}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="17-03">17-03</option>
+                 <option value="25-01" id="selected-cc-exp">25-01</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
+  {
+    description: "Compound cc-exp: {YEAR4}-{MON2}",
+    document: `<form><select autocomplete="cc-exp">
+                 <option value="2017-03">2017-03</option>
+                 <option value="2025-01" id="selected-cc-exp">2025-01</option>
+               </select></form>`,
+    profileData: [Object.assign({}, DEFAULT_CREDITCARD_RECORD)],
+    expectedResult: [DEFAULT_CREDITCARD_RECORD],
+    expectedOptionElements: [{"cc-exp": "selected-cc-exp"}],
+  },
 ];
 
 for (let testcase of TESTCASES) {
   add_task(async function() {
     do_print("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();
-    let adaptedAddresses = handler.getAdaptedProfiles(testcase.profileData);
-    Assert.deepEqual(adaptedAddresses, testcase.expectedResult);
+    let adaptedRecords = handler.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);