Bug 1371139 - Part 1. Implementation of form auto fill credit card layout. r=lchang, seanlee draft
authorRay Lin <ralin@mozilla.com>
Tue, 25 Jul 2017 15:28:38 +0800
changeset 649735 a41686860e9d56007bdefef519273dd64d65344e
parent 649622 7dddbd85047c6dc73ddbe1e423cd643a217845b3
child 649736 c9d0258b05d72871db77a84fcafc7ca88ddecee4
child 649852 06dfe8e27999d6e66739ec2365fe1bf095e6fc53
push id75128
push userbmo:ralin@mozilla.com
push dateMon, 21 Aug 2017 07:31:35 +0000
reviewerslchang, seanlee
bugs1371139
milestone57.0a1
Bug 1371139 - Part 1. Implementation of form auto fill credit card layout. r=lchang, seanlee MozReview-Commit-ID: 59gyOlHvSz8
browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
browser/extensions/formautofill/content/formautofill.xml
browser/extensions/formautofill/content/icon-credit-card-generic.svg
browser/extensions/formautofill/skin/linux/autocomplete-item.css
browser/extensions/formautofill/skin/osx/autocomplete-item.css
browser/extensions/formautofill/skin/shared/autocomplete-item.css
browser/extensions/formautofill/skin/windows/autocomplete-item.css
browser/extensions/formautofill/test/unit/test_profileAutocompleteResult.js
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -269,16 +269,23 @@ class AddressResult extends ProfileAutoC
   }
 }
 
 class CreditCardResult extends ProfileAutoCompleteResult {
   constructor(...args) {
     super(...args);
   }
 
+  _fmtMaskedCreditCardLabel(maskedCCNum = "") {
+    return {
+      affix: "****",
+      label: maskedCCNum.replace(/^\**/, ""),
+    };
+  }
+
   _getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
     const GROUP_FIELDS = {
       "cc-name": [
         "cc-name",
         "cc-given-name",
         "cc-additional-name",
         "cc-family-name",
       ],
@@ -307,16 +314,20 @@ class CreditCardResult extends ProfileAu
         continue;
       }
 
       let matching = GROUP_FIELDS[currentFieldName] ?
         allFieldNames.some(fieldName => GROUP_FIELDS[currentFieldName].includes(fieldName)) :
         allFieldNames.includes(currentFieldName);
 
       if (matching) {
+        if (currentFieldName == "cc-number") {
+          let {affix, label} = this._fmtMaskedCreditCardLabel(profile[currentFieldName]);
+          return affix + label;
+        }
         return profile[currentFieldName];
       }
     }
 
     return ""; // Nothing matched.
   }
 
   _generateLabels(focusedFieldName, allFieldNames, profiles) {
@@ -328,18 +339,27 @@ class CreditCardResult extends ProfileAu
 
       return [FormAutofillUtils.stringBundle.formatStringFromName("insecureFieldWarningDescription", [brandName], 1)];
     }
 
     // Skip results without a primary label.
     let labels = profiles.filter(profile => {
       return !!profile[focusedFieldName];
     }).map(profile => {
+      let primaryAffix;
+      let primary = profile[focusedFieldName];
+
+      if (focusedFieldName == "cc-number") {
+        let {affix, label} = this._fmtMaskedCreditCardLabel(primary);
+        primaryAffix = affix;
+        primary = label;
+      }
       return {
-        primary: profile[focusedFieldName],
+        primaryAffix,
+        primary,
         secondary: this._getSecondaryLabel(focusedFieldName,
                                            allFieldNames,
                                            profile),
       };
     });
     // Add an empty result entry for footer.
     labels.push({primary: "", secondary: ""});
 
@@ -371,9 +391,14 @@ class CreditCardResult extends ProfileAu
     }
 
     if (index == this.matchCount - 1) {
       return "autofill-footer";
     }
 
     return "autofill-profile";
   }
+
+  getImageAt(index) {
+    this._checkIndexBounds(index);
+    return "chrome://formautofill/content/icon-credit-card-generic.svg";
+  }
 }
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -65,46 +65,53 @@
       <method name="_adjustAutofillItemLayout">
         <body>
         <![CDATA[
           let outerBoxRect = this.parentNode.getBoundingClientRect();
 
           // Make item fit in popup as XUL box could not constrain
           // item's width
           this._itemBox.style.width = outerBoxRect.width + "px";
-          // Use two-lines layout when width is smaller than 150px
-          if (outerBoxRect.width <= 150) {
+          // Use two-lines layout when width is smaller than 150px or
+          // 185px if an image precedes the label.
+          let oneLineMinRequiredWidth = this.getAttribute("ac-image") ? 185 : 150;
+
+          if (outerBoxRect.width <= oneLineMinRequiredWidth) {
             this._itemBox.setAttribute("size", "small");
           } else {
             this._itemBox.removeAttribute("size");
           }
         ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
   <binding id="autocomplete-profile-listitem" extends="chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-base">
     <xbl:content xmlns="http://www.w3.org/1999/xhtml">
-      <div anonid="autofill-item-box" class="autofill-item-box">
+      <div anonid="autofill-item-box" class="autofill-item-box" xbl:inherits="ac-image">
         <div class="profile-label-col profile-item-col">
+          <span anonid="profile-label-affix" class="profile-label-affix"></span>
           <span anonid="profile-label" class="profile-label"></span>
         </div>
         <div class="profile-comment-col profile-item-col">
           <span anonid="profile-comment" class="profile-comment"></span>
         </div>
       </div>
     </xbl:content>
 
     <implementation implements="nsIDOMXULSelectControlItemElement">
       <constructor>
         <![CDATA[
           this._itemBox = document.getAnonymousElementByAttribute(
             this, "anonid", "autofill-item-box"
           );
+          this._labelAffix = document.getAnonymousElementByAttribute(
+            this, "anonid", "profile-label-affix"
+          );
           this._label = document.getAnonymousElementByAttribute(
             this, "anonid", "profile-label"
           );
           this._comment = document.getAnonymousElementByAttribute(
             this, "anonid", "profile-comment"
           );
 
           this._adjustAcItem();
@@ -128,19 +135,21 @@
         ]]></setter>
       </property>
 
       <method name="_adjustAcItem">
         <body>
         <![CDATA[
           this._adjustAutofillItemLayout();
           this.setAttribute("formautofillattached", "true");
+          this._itemBox.style.setProperty("--primary-icon", `url(${this.getAttribute("ac-image")})`);
 
-          let {primary, secondary} = JSON.parse(this.getAttribute("ac-value"));
+          let {primaryAffix, primary, secondary} = JSON.parse(this.getAttribute("ac-value"));
 
+          this._labelAffix.textContent = primaryAffix;
           this._label.textContent = primary;
           this._comment.textContent = secondary;
         ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/content/icon-credit-card-generic.svg
@@ -0,0 +1,9 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M4.5,9.4H3.2c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h1.3c0.3,0,0.5-0.2,0.5-0.5S4.8,9.4,4.5,9.4z"/>
+  <path fill="context-fill" d="M9.3,9.4H6.2c-0.3,0-0.5,0.2-0.5,0.5s0.2,0.5,0.5,0.5h3.2c0.3,0,0.5-0.2,0.5-0.5S9.6,9.4,9.3,9.4z"/>
+  <path fill="context-fill" d="M14,2H2C0.9,2,0,2.9,0,4v8c0,1.1,0.9,2,2,2h12c1.1,0,2-0.9,2-2V4C16,2.9,15.1,2,14,2z M14,12H2V7.7h12V12z
+	  M14,6H2V4h12V6z"/>
+</svg>
--- a/browser/extensions/formautofill/skin/linux/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/linux/autocomplete-item.css
@@ -1,22 +1,10 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @namespace url("http://www.w3.org/1999/xhtml");
 
 
-.autofill-item-box > .profile-item-col > .profile-label {
-  font-size: .84em;
-}
-
-.autofill-item-box > .profile-item-col > .profile-comment {
-  font-size: .7em;
+.autofill-item-box {
+  --default-font-size: 14.25;
 }
-
-.autofill-footer > .autofill-warning {
-  font-size: .7em;
-}
-
-.autofill-footer > .autofill-option-button {
-  font-size: .77em;
-}
--- a/browser/extensions/formautofill/skin/osx/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/osx/autocomplete-item.css
@@ -1,18 +1,9 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @namespace url("http://www.w3.org/1999/xhtml");
 
-
-.autofill-item-box > .profile-item-col > .profile-label {
-  font-size: 1.09em;
+.autofill-item-box {
+  --default-font-size: 11;
 }
-
-.autofill-item-box > .profile-item-col > .profile-comment {
-  font-size: .9em;
-}
-
-.autofill-footer > .autofill-warning {
-  font-size: .9em;
-}
--- a/browser/extensions/formautofill/skin/shared/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/shared/autocomplete-item.css
@@ -19,26 +19,40 @@ xul|richlistitem[originaltype="autofill-
   background-color: var(--arrowpanel-dimmed);
 }
 
 .autofill-item-box {
   --item-padding-vertical: 6px;
   --item-padding-horizontal: 10px;
   --col-spacer: 7px;
   --item-width: calc(50% - (var(--col-spacer) / 2));
-  --item-text-color: -moz-FieldText;
+  --label-text-color: #262626;
+  --comment-text-color: #646464;
+  --warning-text-color: #646464;
+  --option-btn-text-color: -moz-FieldText;
+
+  --default-font-size: 12;
+  --label-affix-font-size: 10;
+  --label-font-size: 12;
+  --comment-font-size: 10;
+  --warning-font-size: 10;
+  --option-btn-font-size: 11;
 }
 
 .autofill-item-box[size="small"] {
   --item-padding-vertical: 7px;
   --col-spacer: 0px;
   --row-spacer: 3px;
   --item-width: 100%;
 }
 
+.autofill-item-box:not([ac-image=""]) {
+  --comment-font-size: 11;
+}
+
 .autofill-footer,
 .autofill-footer[size="small"] {
   --item-width: 100%;
   --item-padding-vertical: 0;
   --item-padding-horizontal: 0;
 }
 
 .autofill-item-box {
@@ -48,17 +62,17 @@ xul|richlistitem[originaltype="autofill-
   padding: var(--item-padding-vertical) 0;
   padding-inline-start: var(--item-padding-horizontal);
   padding-inline-end: var(--item-padding-horizontal);
   display: flex;
   flex-direction: row;
   flex-wrap: wrap;
   align-items: center;
   background-color: #FFFFFF;
-  color: var(--item-text-color);
+  color: var(--label-text-color);
 }
 
 .autofill-item-box:last-child {
   border-bottom: 0;
 }
 
 .autofill-item-box > .profile-item-col {
   box-sizing: border-box;
@@ -67,20 +81,45 @@ xul|richlistitem[originaltype="autofill-
   white-space: nowrap;
   width: var(--item-width);
 }
 
 .autofill-item-box > .profile-label-col {
   text-align: start;
 }
 
+.autofill-item-box:not([ac-image=""]) > .profile-label-col::before {
+  margin-right: 5px;
+  float: left;
+  content: "";
+  width: 16px;
+  height: 16px;
+  background-image: var(--primary-icon);
+  background-size: 16px 16px;
+  -moz-context-properties: fill;
+  fill: #4D4D4D;
+}
+
+.autofill-item-box > .profile-label-col > .profile-label {
+  font-size: calc(var(--label-font-size) / var(--default-font-size) * 1em);
+}
+
+.autofill-item-box > .profile-label-col > .profile-label-affix {
+  font-weight: lighter;
+  font-size: calc(var(--label-affix-font-size) / var(--default-font-size) * 1em);
+}
+
 .autofill-item-box > .profile-comment-col {
   margin-inline-start: var(--col-spacer);
   text-align: end;
-  color: GrayText;
+  color: var(--comment-text-color);
+}
+
+.autofill-item-box > .profile-comment-col > .profile-comment {
+  font-size: calc(var(--comment-font-size) / var(--default-font-size) * 1em);
 }
 
 .autofill-item-box[size="small"] {
   flex-direction: column;
 }
 
 .autofill-item-box[size="small"] > .profile-comment-col {
   margin-top: var(--row-spacer);
@@ -95,25 +134,28 @@ xul|richlistitem[originaltype="autofill-
   display: flex;
   justify-content: center;
   align-items: center;
   width: var(--item-width);
 }
 
 .autofill-footer > .autofill-warning {
   padding: 2.5px 0;
-  color: #737373;
+  color: var(--warning-text-color);
   text-align: center;
   background-color: rgba(248,232,28,.2);
   border-bottom: 1px solid rgba(38,38,38,.15);
+  font-size: calc(var(--warning-font-size) / var(--default-font-size) * 1em);
 }
 
 .autofill-footer > .autofill-option-button {
-  height: 41px;
+  height: 40px;
   background-color: #EDEDED;
+  font-size: calc(var(--option-btn-font-size) / var(--default-font-size) * 1em);
+  color: var(--option-btn-text-color);
 }
 
 .autofill-footer[no-warning="true"] > .autofill-warning {
   display: none;
 }
 
 .autofill-insecure-item {
   box-sizing: border-box;
--- a/browser/extensions/formautofill/skin/windows/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/windows/autocomplete-item.css
@@ -1,25 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @namespace url("http://www.w3.org/1999/xhtml");
 @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
-
-.autofill-item-box > .profile-item-col > .profile-comment {
-  font-size: .83em;
-}
-
-.autofill-footer > .autofill-warning {
-  font-size: .83em;
-}
-
-.autofill-footer > .autofill-option-button {
-  font-size: .91em;
+.autofill-item-box {
+  --default-font-size: 12;
 }
 
 @media (-moz-windows-default-theme: 0) {
   xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box {
     background-color: Highlight;
   }
+
+  .autofill-item-box {
+    --label-text-color: -moz-FieldText;
+    --comment-text-color: GrayText;
+  }
 }
--- a/browser/extensions/formautofill/test/unit/test_profileAutocompleteResult.js
+++ b/browser/extensions/formautofill/test/unit/test_profileAutocompleteResult.js
@@ -253,28 +253,28 @@ let creditCardTestCases = [{
     searchResult: Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
     defaultIndex: 0,
     items: [{
       value: "",
       style: "autofill-profile",
       comment: JSON.stringify(matchingProfiles[0]),
       label: JSON.stringify({
         primary: "Timothy Berners-Lee",
-        secondary: "************6785",
+        secondary: "****6785",
       }),
-      image: "",
+      image: "chrome://formautofill/content/icon-credit-card-generic.svg",
     }, {
       value: "",
       style: "autofill-profile",
       comment: JSON.stringify(matchingProfiles[1]),
       label: JSON.stringify({
         primary: "John Doe",
-        secondary: "************1234",
+        secondary: "****1234",
       }),
-      image: "",
+      image: "chrome://formautofill/content/icon-credit-card-generic.svg",
     }],
   },
 }, {
   description: "Focus on a `cc-number` field",
   options: {},
   matchingProfiles,
   allFieldNames,
   searchString: "",
@@ -282,38 +282,41 @@ let creditCardTestCases = [{
   expected: {
     searchResult: Ci.nsIAutoCompleteResult.RESULT_SUCCESS,
     defaultIndex: 0,
     items: [{
       value: "",
       style: "autofill-profile",
       comment: JSON.stringify(matchingProfiles[0]),
       label: JSON.stringify({
-        primary: "************6785",
+        primaryAffix: "****",
+        primary: "6785",
         secondary: "Timothy Berners-Lee",
       }),
-      image: "",
+      image: "chrome://formautofill/content/icon-credit-card-generic.svg",
     }, {
       value: "",
       style: "autofill-profile",
       comment: JSON.stringify(matchingProfiles[1]),
       label: JSON.stringify({
-        primary: "************1234",
+        primaryAffix: "****",
+        primary: "1234",
         secondary: "John Doe",
       }),
-      image: "",
+      image: "chrome://formautofill/content/icon-credit-card-generic.svg",
     }, {
       value: "",
       style: "autofill-profile",
       comment: JSON.stringify(matchingProfiles[2]),
       label: JSON.stringify({
-        primary: "************5678",
+        primaryAffix: "****",
+        primary: "5678",
         secondary: "",
       }),
-      image: "",
+      image: "chrome://formautofill/content/icon-credit-card-generic.svg",
     }],
   },
 }, {
   description: "No matching profiles",
   options: {},
   matchingProfiles: [],
   allFieldNames,
   searchString: "",