Bug 1390433 - (From 1371149)Part 1. Show insecure field in credit card autofill dropdown instead of result when the connection is not secure. r=MattN
MozReview-Commit-ID: APjaTedWUz9
--- 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) {
@@ -95,17 +97,17 @@ AutofillProfileAutoCompleteSearch.protot
let savedFieldNames = FormAutofillContent.savedFieldNames;
let focusedInput = formFillController.focusedInput;
let info = FormAutofillContent.getInputDetails(focusedInput);
let isAddressField = FormAutofillUtils.isAddressField(info.fieldName);
let handler = FormAutofillContent.getFormHandler(focusedInput);
let allFieldNames = handler.allFieldNames;
- let filledRecordGUID = isAddressField ? handler.address.filledRecordGUID : handler.creditCards.filledRecordGUID;
+ let filledRecordGUID = isAddressField ? handler.address.filledRecordGUID : handler.creditCard.filledRecordGUID;
// Fallback to form-history if ...
// - no profile can fill the currently-focused input.
// - the current form has already been populated.
// - (address only) less than 3 inputs are covered by all saved fields in the storage.
if (!savedFieldNames.has(info.fieldName) || filledRecordGUID || (isAddressField &&
allFieldNames.filter(field => savedFieldNames.has(field)).length < FormAutofillUtils.AUTOFILL_FIELDS_THRESHOLD)) {
let formHistory = Cc["@mozilla.org/autocomplete/search;1?name=form-history"]
@@ -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
@@ -428,8 +428,12 @@ this.FormAutofillUtils = {
};
XPCOMUtils.defineLazyGetter(this.FormAutofillUtils, "DEFAULT_COUNTRY_CODE", () => {
return Services.prefs.getCharPref("browser.search.countryCode", "US");
});
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 "autofill-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
@@ -1,31 +1,41 @@
/* 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/. */
#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-profile"],
-#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"] {
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"],
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-insecureWarning"] {
display: block;
margin: 0;
padding: 0;
height: auto;
min-height: auto;
}
#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="autofill-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"] {
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"][collapsed="true"],
+#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-insecureWarning"][collapsed="true"] {
display: none;
}
#PopupAutoComplete[firstresultstyle="autofill-profile"] {
min-width: 150px !important;
}
+
+#PopupAutoComplete[firstresultstyle="autofill-insecureWarning"] {
+ min-width: 200px !important;
+}
--- 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,48 @@
this._itemBox.setAttribute("no-warning", "true");
}
]]>
</body>
</method>
</implementation>
</binding>
+ <binding id="autocomplete-creditcard-insecure-field" 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-insecure-item">
+ </div>
+ </xbl:content>
+
+ <implementation implements="nsIDOMXULSelectControlItemElement">
+ <constructor>
+ <![CDATA[
+ this._itemBox = document.getAnonymousElementByAttribute(
+ this, "anonid", "autofill-item-box"
+ );
+
+ this._adjustAcItem();
+ ]]>
+ </constructor>
+
+ <property name="selected" onget="return this.getAttribute('selected') == 'true';">
+ <setter><![CDATA[
+ // Make this item unselectable since we see this item as a pure message.
+ return false;
+ ]]></setter>
+ </property>
+
+ <method name="_adjustAcItem">
+ <body>
+ <![CDATA[
+ this._adjustAutofillItemLayout();
+ this.setAttribute("formautofillattached", "true");
+
+ let value = this.getAttribute("ac-value");
+ this._itemBox.textContent = value;
+ ]]>
+ </body>
+ </method>
+
+ </implementation>
+ </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
--- a/browser/extensions/formautofill/skin/shared/autocomplete-item.css
+++ b/browser/extensions/formautofill/skin/shared/autocomplete-item.css
@@ -9,16 +9,21 @@
xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box {
background-color: #F2F2F2;
}
xul|richlistitem[originaltype="autofill-footer"][selected="true"] > .autofill-item-box > .autofill-option-button {
background-color: #DCDCDE;
}
+xul|richlistitem[originaltype="autofill-insecureWarning"] {
+ border-bottom: 1px solid var(--panel-separator-color);
+ 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;
}
@@ -104,8 +109,30 @@ xul|richlistitem[originaltype="autofill-
.autofill-footer > .autofill-option-button {
height: 41px;
background-color: #EDEDED;
}
.autofill-footer[no-warning="true"] > .autofill-warning {
display: none;
}
+
+.autofill-insecure-item {
+ box-sizing: border-box;
+ padding: 4px 0;
+ display: flex;
+ flex-direction: row;
+ flex-wrap: nowrap;
+ align-items: center;
+ color: GrayText;
+}
+
+.autofill-insecure-item::before {
+ display: block;
+ margin-inline-start: 4px;
+ margin-inline-end: 8px;
+ content: "";
+ width: 16px;
+ height: 16px;
+ background-image: url(chrome://browser/skin/connection-mixed-active-loaded.svg);
+ -moz-context-properties: fill;
+ fill: GrayText;
+}