Bug 1359892 - [Form Autofill] Support full-name fields. r=MattN draft
authorLuke Chang <lchang@mozilla.com>
Thu, 13 Apr 2017 17:12:59 +0800
changeset 570490 2d595af30b8851855cf056120422d71f335d4041
parent 570483 1c994cd72265c48bbdc9f6404df87c4eae488b34
child 570494 3697f6e8a8c1337f4964f9c567631c277a25132a
push id56499
push userbmo:lchang@mozilla.com
push dateFri, 28 Apr 2017 23:13:47 +0000
reviewersMattN
bugs1359892
milestone55.0a1
Bug 1359892 - [Form Autofill] Support full-name fields. r=MattN MozReview-Commit-ID: 3G3d5nv6j7v
browser/extensions/formautofill/FormAutofillHeuristics.jsm
browser/extensions/formautofill/FormAutofillNameUtils.jsm
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
browser/extensions/formautofill/ProfileStorage.jsm
browser/extensions/formautofill/test/unit/test_profileAutocompleteResult.js
browser/extensions/formautofill/test/unit/test_transformFields.js
--- a/browser/extensions/formautofill/FormAutofillHeuristics.jsm
+++ b/browser/extensions/formautofill/FormAutofillHeuristics.jsm
@@ -12,16 +12,17 @@ this.EXPORTED_SYMBOLS = ["FormAutofillHe
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 /**
  * Returns the autocomplete information of fields according to heuristics.
  */
 this.FormAutofillHeuristics = {
   VALID_FIELDS: [
+    "name",
     "given-name",
     "additional-name",
     "family-name",
     "organization",
     "street-address",
     "address-line1",
     "address-line2",
     "address-line3",
--- a/browser/extensions/formautofill/FormAutofillNameUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillNameUtils.jsm
@@ -121,16 +121,20 @@ var FormAutofillNameUtils = {
     // no more than 2 words.
     //
     // Chinese and Japanese names are usually spelled out using the Han
     // characters (logographs), which constitute the "CJK Unified Ideographs"
     // block in Unicode, also referred to as Unihan. Korean names are usually
     // spelled out in the Korean alphabet (Hangul), although they do have a Han
     // equivalent as well.
 
+    if (!name) {
+      return false;
+    }
+
     let previousWasCJK = false;
     let wordCount = 0;
 
     for (let c of name) {
       let isMiddleDot = this.MIDDLE_DOT.includes(c);
       let isCJK = !isMiddleDot && this.reCJK.test(c);
       if (!isCJK && !isMiddleDot && !this.WHITESPACE.includes(c)) {
         return false;
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -16,29 +16,16 @@ this.FormAutofillUtils = {
       let ConsoleAPI = Cu.import("resource://gre/modules/Console.jsm", {}).ConsoleAPI;
       return new ConsoleAPI({
         maxLogLevelPref: "browser.formautofill.loglevel",
         prefix: logPrefix,
       });
     });
   },
 
-  generateFullName(firstName, lastName, middleName) {
-    // TODO: The implementation should depend on the L10N spec, but a simplified
-    // rule is used here.
-    let fullName = firstName;
-    if (middleName) {
-      fullName += " " + middleName;
-    }
-    if (lastName) {
-      fullName += " " + lastName;
-    }
-    return fullName;
-  },
-
   findLabelElements(element) {
     let document = element.ownerDocument;
     let labels = [];
     // TODO: querySelectorAll is inefficient here. However, bug 1339726 is for
     // a more efficient implementation from DOM API perspective. This function
     // should be refined after input.labels API landed.
     for (let label of document.querySelectorAll("label[for]")) {
       if (element.id == label.htmlFor) {
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -95,52 +95,43 @@ ProfileAutoCompleteResult.prototype = {
       "given-name",
       "additional-name",
       "family-name",
     ];
 
     focusedFieldName = possibleNameFields.includes(focusedFieldName) ?
                        "name" : focusedFieldName;
 
-    // Clones the profile to avoid exposing our modification.
-    let clonedProfile = Object.assign({}, profile);
-    if (!clonedProfile.name) {
-      clonedProfile.name =
-        FormAutofillUtils.generateFullName(clonedProfile["given-name"],
-                                           clonedProfile["family-name"],
-                                           clonedProfile["additional-name"]);
-    }
-
     const secondaryLabelOrder = [
       "street-address",  // Street address
       "name",            // Full name
       "address-level2",  // City/Town
       "organization",    // Company or organization name
       "address-level1",  // Province/State (Standardized code if possible)
       "country",         // Country
       "postal-code",     // Postal code
       "tel",             // Phone number
       "email",           // Email address
     ];
 
     for (const currentFieldName of secondaryLabelOrder) {
       if (focusedFieldName == currentFieldName ||
-          !clonedProfile[currentFieldName]) {
+          !profile[currentFieldName]) {
         continue;
       }
 
       let matching;
       if (currentFieldName == "name") {
         matching = allFieldNames.some(fieldName => possibleNameFields.includes(fieldName));
       } else {
         matching = allFieldNames.includes(currentFieldName);
       }
 
       if (matching) {
-        return clonedProfile[currentFieldName];
+        return profile[currentFieldName];
       }
     }
 
     return ""; // Nothing matched.
   },
 
   _generateLabels(focusedFieldName, allFieldNames, profiles) {
     // Skip results without a primary label.
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -295,16 +295,24 @@ ProfileStorage.prototype = {
     return Object.assign({}, profile);
   },
 
   _findByGUID(guid) {
     return this._store.data.profiles.find(profile => profile.guid == guid);
   },
 
   _computeFields(profile) {
+    // Compute name
+    profile.name = FormAutofillNameUtils.joinNameParts({
+      given: profile["given-name"],
+      middle: profile["additional-name"],
+      family: profile["family-name"],
+    });
+
+    // Compute address
     if (profile["street-address"]) {
       let streetAddress = profile["street-address"].split("\n");
       // TODO: we should prevent the dataloss by concatenating the rest of lines
       //       with a locale-specific character in the future (bug 1360114).
       for (let i = 0; i < 3; i++) {
         if (streetAddress[i]) {
           profile["address-line" + (i + 1)] = streetAddress[i];
         }
@@ -332,17 +340,36 @@ ProfileStorage.prototype = {
 
       // Concatenate "address-line*" if "street-address" is omitted.
       if (!profile["street-address"]) {
         profile["street-address"] = addressLines.join("\n");
       }
     }
   },
 
+  _normalizeName(profile) {
+    if (!profile.name) {
+      return;
+    }
+
+    let nameParts = FormAutofillNameUtils.splitName(profile.name);
+    if (!profile["given-name"] && nameParts.given) {
+      profile["given-name"] = nameParts.given;
+    }
+    if (!profile["additional-name"] && nameParts.middle) {
+      profile["additional-name"] = nameParts.middle;
+    }
+    if (!profile["family-name"] && nameParts.family) {
+      profile["family-name"] = nameParts.family;
+    }
+    delete profile.name;
+  },
+
   _normalizeProfile(profile) {
+    this._normalizeName(profile);
     this._normalizeAddress(profile);
 
     for (let key in profile) {
       if (!VALID_FIELDS.includes(key)) {
         throw new Error(`"${key}" is not a valid field.`);
       }
       if (typeof profile[key] !== "string" &&
           typeof profile[key] !== "number") {
--- a/browser/extensions/formautofill/test/unit/test_profileAutocompleteResult.js
+++ b/browser/extensions/formautofill/test/unit/test_profileAutocompleteResult.js
@@ -1,23 +1,25 @@
 "use strict";
 
 Cu.import("resource://formautofill/ProfileAutoCompleteResult.jsm");
 
 let matchingProfiles = [{
   guid: "test-guid-1",
   "given-name": "Timothy",
   "family-name": "Berners-Lee",
+  name: "Timothy Berners-Lee",
   organization: "Sesame Street",
   "street-address": "123 Sesame Street.",
   tel: "1-345-345-3456.",
 }, {
   guid: "test-guid-2",
   "given-name": "John",
   "family-name": "Doe",
+  name: "John Doe",
   organization: "Mozilla",
   "street-address": "331 E. Evelyn Avenue",
   tel: "1-650-903-0800",
 }, {
   guid: "test-guid-3",
   organization: "",
   "street-address": "321, No Name St.",
   tel: "1-000-000-0000",
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -14,16 +14,32 @@ const COMPUTE_TESTCASES = [
   {
     description: "Empty profile",
     profile: {
     },
     expectedResult: {
     },
   },
 
+  // Name
+  {
+    description: "Has split names",
+    profile: {
+      "given-name": "Timothy",
+      "additional-name": "John",
+      "family-name": "Berners-Lee",
+    },
+    expectedResult: {
+      "given-name": "Timothy",
+      "additional-name": "John",
+      "family-name": "Berners-Lee",
+      "name": "Timothy John Berners-Lee",
+    },
+  },
+
   // Address
   {
     description: "\"street-address\" with single line",
     profile: {
       "street-address": "single line",
     },
     expectedResult: {
       "street-address": "single line",
@@ -73,16 +89,55 @@ const NORMALIZE_TESTCASES = [
   {
     description: "Empty profile",
     profile: {
     },
     expectedResult: {
     },
   },
 
+  // Name
+  {
+    description: "Has \"name\", and the split names are omitted",
+    profile: {
+      "name": "Timothy John Berners-Lee",
+    },
+    expectedResult: {
+      "given-name": "Timothy",
+      "additional-name": "John",
+      "family-name": "Berners-Lee",
+    },
+  },
+  {
+    description: "Has both \"name\" and split names",
+    profile: {
+      "name": "John Doe",
+      "given-name": "Timothy",
+      "additional-name": "John",
+      "family-name": "Berners-Lee",
+    },
+    expectedResult: {
+      "given-name": "Timothy",
+      "additional-name": "John",
+      "family-name": "Berners-Lee",
+    },
+  },
+  {
+    description: "Has \"name\", and some of split names are omitted",
+    profile: {
+      "name": "John Doe",
+      "given-name": "Timothy",
+    },
+    expectedResult: {
+      "given-name": "Timothy",
+      "family-name": "Doe",
+    },
+  },
+
+
   // Address
   {
     description: "Has \"address-line1~3\" and \"street-address\" is omitted",
     profile: {
       "address-line1": "line1",
       "address-line2": "line2",
       "address-line3": "line3",
     },