Bug 1371149 - Part 1. Show insecure field in credit card autofill dropdown instead of result when the connection is not secure. r=MattN,seanlee
☠☠ backed out by 91b30f63e8d8 ☠ ☠
authorRay Lin <ralin@mozilla.com>
Fri, 30 Jun 2017 09:51:02 -0700
changeset 642631 fce47fd69d944bc43de3875666a425ef91886017
parent 642630 a5db792ef42a7a3f7422206a58d1a94953c93d5a
child 642632 4c4c732eef949cb4cc34b8e6f10e04830ca9b73e
push id72833
push userbmo:emilio+bugs@crisal.io
push dateTue, 08 Aug 2017 16:50:16 +0000
reviewersMattN, seanlee
bugs1371149
milestone57.0a1
Bug 1371149 - Part 1. Show insecure field in credit card autofill dropdown instead of result when the connection is not secure. r=MattN,seanlee MozReview-Commit-ID: APjaTedWUz9
browser/extensions/formautofill/FormAutofillContent.jsm
browser/extensions/formautofill/FormAutofillUtils.jsm
browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
browser/extensions/formautofill/content/formautofill.css
browser/extensions/formautofill/content/formautofill.xml
browser/extensions/formautofill/locales/en-US/formautofill.properties
--- a/browser/extensions/formautofill/FormAutofillContent.jsm
+++ b/browser/extensions/formautofill/FormAutofillContent.jsm
@@ -22,16 +22,18 @@ Cu.import("resource://formautofill/FormA
 XPCOMUtils.defineLazyModuleGetter(this, "AddressResult",
                                   "resource://formautofill/ProfileAutoCompleteResult.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CreditCardResult",
                                   "resource://formautofill/ProfileAutoCompleteResult.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillHandler",
                                   "resource://formautofill/FormAutofillHandler.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormLikeFactory",
                                   "resource://gre/modules/FormLikeFactory.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "InsecurePasswordUtils",
+                                  "resource://gre/modules/InsecurePasswordUtils.jsm");
 
 const formFillController = Cc["@mozilla.org/satchel/form-fill-controller;1"]
                              .getService(Ci.nsIFormFillController);
 
 // Register/unregister a constructor as a factory.
 function AutocompleteFactory() {}
 AutocompleteFactory.prototype = {
   register(targetConstructor) {
@@ -132,21 +134,23 @@ AutofillProfileAutoCompleteSearch.protot
       let result = null;
       if (isAddressField) {
         result = new AddressResult(searchString,
                                    info.fieldName,
                                    allFieldNames,
                                    adaptedRecords,
                                    {});
       } else {
+        let isSecure = InsecurePasswordUtils.isFormSecure(handler.form);
+
         result = new CreditCardResult(searchString,
                                       info.fieldName,
                                       allFieldNames,
                                       adaptedRecords,
-                                      {});
+                                      {isSecure});
       }
       listener.onSearchResult(this, result);
       ProfileAutocomplete.setProfileAutoCompleteResult(result);
     });
   },
 
   /**
    * Stops an asynchronous search that is in progress
--- a/browser/extensions/formautofill/FormAutofillUtils.jsm
+++ b/browser/extensions/formautofill/FormAutofillUtils.jsm
@@ -341,8 +341,12 @@ this.FormAutofillUtils = {
       element.textContent = bundle.GetStringFromName(element.getAttribute("data-localization"));
       element.removeAttribute("data-localization");
     }
   },
 };
 
 this.log = null;
 this.FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
+
+XPCOMUtils.defineLazyGetter(FormAutofillUtils, "stringBundle", function() {
+  return Services.strings.createBundle("chrome://formautofill/locale/formautofill.properties");
+});
--- a/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
+++ b/browser/extensions/formautofill/ProfileAutoCompleteResult.jsm
@@ -3,24 +3,33 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 this.EXPORTED_SYMBOLS = ["AddressResult", "CreditCardResult"]; /* exported AddressResult, CreditCardResult */
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://formautofill/FormAutofillUtils.jsm");
 
+XPCOMUtils.defineLazyGetter(this, "gBrandBundle", function() {
+  return Services.strings.createBundle("chrome://branding/locale/brand.properties");
+});
+XPCOMUtils.defineLazyPreferenceGetter(this, "insecureWarningEnabled", "security.insecure_field_warning.contextual.enabled");
+
 this.log = null;
 FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
 
 class ProfileAutoCompleteResult {
-  constructor(searchString, focusedFieldName, allFieldNames, matchingProfiles, {resultCode = null}) {
+  constructor(searchString, focusedFieldName, allFieldNames, matchingProfiles, {
+    resultCode = null,
+    isSecure = true,
+  }) {
     log.debug("Constructing new ProfileAutoCompleteResult:", [...arguments]);
 
     // nsISupports
     this.QueryInterface = XPCOMUtils.generateQI([Ci.nsIAutoCompleteResult]);
 
     // The user's query string
     this.searchString = searchString;
     // The field name of the focused input.
@@ -28,16 +37,18 @@ class ProfileAutoCompleteResult {
     // All field names in the form which contains the focused input.
     this._allFieldNames = allFieldNames;
     // The matching profiles contains the information for filling forms.
     this._matchingProfiles = matchingProfiles;
     // The default item that should be entered if none is selected
     this.defaultIndex = 0;
     // The reason the search failed
     this.errorDescription = "";
+    // The value used to determine whether the form is secure or not.
+    this._isSecure = isSecure;
 
     // The result code of this result object.
     if (resultCode) {
       this.searchResult = resultCode;
     } else if (matchingProfiles.length > 0) {
       this.searchResult = Ci.nsIAutoCompleteResult.RESULT_SUCCESS;
     } else {
       this.searchResult = Ci.nsIAutoCompleteResult.RESULT_NOMATCH;
@@ -142,27 +153,16 @@ class ProfileAutoCompleteResult {
   removeValueAt(index, removeFromDatabase) {
     // There is no plan to support removing profiles via autocomplete.
   }
 }
 
 class AddressResult extends ProfileAutoCompleteResult {
   constructor(...args) {
     super(...args);
-
-    // Add an empty result entry for footer. Its content will come from
-    // the footer binding, so don't assign any value to it.
-    // The additional properties: categories and focusedCategory are required of
-    // the popup to generate autofill hint on the footer.
-    this._popupLabels.push({
-      primary: "",
-      secondary: "",
-      categories: FormAutofillUtils.getCategoriesFromFieldNames(this._allFieldNames),
-      focusedCategory: FormAutofillUtils.getCategoryFromFieldName(this._focusedFieldName),
-    });
   }
 
   _getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
     // We group similar fields into the same field name so we won't pick another
     // field in the same group as the secondary label.
     const GROUP_FIELDS = {
       "name": [
         "name",
@@ -228,42 +228,49 @@ class AddressResult extends ProfileAutoC
       }
     }
 
     return ""; // Nothing matched.
   }
 
   _generateLabels(focusedFieldName, allFieldNames, profiles) {
     // Skip results without a primary label.
-    return profiles.filter(profile => {
+    let labels = profiles.filter(profile => {
       return !!profile[focusedFieldName];
     }).map(profile => {
       let primaryLabel = profile[focusedFieldName];
       if (focusedFieldName == "street-address" &&
           profile["-moz-street-address-one-line"]) {
         primaryLabel = profile["-moz-street-address-one-line"];
       }
       return {
         primary: primaryLabel,
         secondary: this._getSecondaryLabel(focusedFieldName,
                                            allFieldNames,
                                            profile),
       };
     });
-  }
+    // Add an empty result entry for footer. Its content will come from
+    // the footer binding, so don't assign any value to it.
+    // The additional properties: categories and focusedCategory are required of
+    // the popup to generate autofill hint on the footer.
+    labels.push({
+      primary: "",
+      secondary: "",
+      categories: FormAutofillUtils.getCategoriesFromFieldNames(this._allFieldNames),
+      focusedCategory: FormAutofillUtils.getCategoryFromFieldName(this._focusedFieldName),
+    });
 
-
+    return labels;
+  }
 }
 
 class CreditCardResult extends ProfileAutoCompleteResult {
   constructor(...args) {
     super(...args);
-
-    // Add an empty result entry for footer.
-    this._popupLabels.push({primary: "", secondary: ""});
   }
 
   _getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
     const GROUP_FIELDS = {
       "cc-name": [
         "cc-name",
         "cc-given-name",
         "cc-additional-name",
@@ -302,29 +309,65 @@ class CreditCardResult extends ProfileAu
         return profile[currentFieldName];
       }
     }
 
     return ""; // Nothing matched.
   }
 
   _generateLabels(focusedFieldName, allFieldNames, profiles) {
+    if (!this._isSecure) {
+      if (!insecureWarningEnabled) {
+        return [];
+      }
+      let brandName = gBrandBundle.GetStringFromName("brandShortName");
+
+      return [FormAutofillUtils.stringBundle.formatStringFromName("insecureFieldWarningDescription", [brandName], 1)];
+    }
+
     // Skip results without a primary label.
-    return profiles.filter(profile => {
+    let labels = profiles.filter(profile => {
       return !!profile[focusedFieldName];
     }).map(profile => {
       return {
         primary: profile[focusedFieldName],
         secondary: this._getSecondaryLabel(focusedFieldName,
                                            allFieldNames,
                                            profile),
       };
     });
+    // Add an empty result entry for footer.
+    labels.push({primary: "", secondary: ""});
+
+    return labels;
   }
 
   // Always return empty string for credit card result. Since the decryption might
-  // be required of users' input, we have to to suppress AutoCompleteController
+  // be required of users' input, we have to suppress AutoCompleteController
   // from filling encrypted data directly.
   getValueAt(index) {
     this._checkIndexBounds(index);
     return "";
   }
+
+  getLabelAt(index) {
+    this._checkIndexBounds(index);
+
+    let label = this._popupLabels[index];
+    if (typeof label == "string") {
+      return label;
+    }
+    return JSON.stringify(label);
+  }
+
+  getStyleAt(index) {
+    this._checkIndexBounds(index);
+    if (!this._isSecure && insecureWarningEnabled) {
+      return "insecureWarning";
+    }
+
+    if (index == this.matchCount - 1) {
+      return "autofill-footer";
+    }
+
+    return "autofill-profile";
+  }
 }
--- a/browser/extensions/formautofill/content/formautofill.css
+++ b/browser/extensions/formautofill/content/formautofill.css
@@ -14,16 +14,20 @@
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-profile"] {
   -moz-binding: url("chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem");
 }
 
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"] {
   -moz-binding: url("chrome://formautofill/content/formautofill.xml#autocomplete-profile-listitem-footer");
 }
 
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="insecureWarning"] {
+  -moz-binding: url("chrome://formautofill/content/formautofill.xml#autocomplete-creditcard-insecure-field");
+}
+
 /* Treat @collpased="true" as display: none similar to how it is for XUL elements.
  * https://developer.mozilla.org/en-US/docs/Web/CSS/visibility#Values */
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-profile"][collapsed="true"],
 #PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"][collapsed="true"] {
   display: none;
 }
 
 #PopupAutoComplete[firstresultstyle="autofill-profile"] {
--- a/browser/extensions/formautofill/content/formautofill.xml
+++ b/browser/extensions/formautofill/content/formautofill.xml
@@ -53,16 +53,20 @@
       <method name="_onOverflow">
         <body></body>
       </method>
 
       <method name="_onUnderflow">
         <body></body>
       </method>
 
+      <method name="handleOverUnderflow">
+        <body></body>
+      </method>
+
       <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";
@@ -271,9 +275,36 @@
             this._itemBox.setAttribute("no-warning", "true");
           }
         ]]>
         </body>
       </method>
     </implementation>
   </binding>
 
+  <binding id="autocomplete-creditcard-insecure-field" extends="chrome://global/content/bindings/autocomplete.xml#autocomplete-richlistitem-insecure-field">
+    <implementation implements="nsIDOMXULSelectControlItemElement">
+      <constructor>
+      <![CDATA[
+        this.setAttribute("formautofillattached", "true");
+      ]]>
+      </constructor>
+
+      <method name="_cleanup">
+        <body>
+        <![CDATA[
+          this.removeAttribute("formautofillattached");
+        ]]>
+        </body>
+      </method>
+    </implementation>
+
+    <handlers>
+      <handler event="click" button="0" phase="capturing"><![CDATA[
+        // Suppress any other handlers from its inheritance chain.
+        event.stopPropagation();
+
+        // Leave empty here until we're sure where to redirect for "Learn more" link
+      ]]></handler>
+    </handlers>
+  </binding>
+
 </bindings>
--- a/browser/extensions/formautofill/locales/en-US/formautofill.properties
+++ b/browser/extensions/formautofill/locales/en-US/formautofill.properties
@@ -51,8 +51,10 @@ state = State
 postalCode = Postal Code
 zip = Zip Code
 country = Country or Region
 tel = Phone
 email = Email
 cancel = Cancel
 save = Save
 countryWarningMessage = Autofill is currently available only for US addresses
+
+insecureFieldWarningDescription = %S has detected an insecure site. Credit card autofill is temporarily disabled