Bug 1881582, implement the address autofill warning on the popup as a separate row and remove the implementation based on the custom class MozAutocompleteProfileListitemFooter, r=credential-management-reviewers,fluent-reviewers,desktop-theme-reviewers,reusable-components-reviewers,mstriemer,bolsson,sgalich,dao

Differential Revision: https://phabricator.services.mozilla.com/D202750
This commit is contained in:
Neil Deakin 2024-03-18 16:34:20 +00:00
parent 1e83118947
commit e94e76f935
22 changed files with 268 additions and 312 deletions

View file

@ -155,146 +155,6 @@
{ extends: "richlistitem" }
);
class MozAutocompleteProfileListitemFooter extends MozAutocompleteProfileListitemBase {
static get markup() {
return `
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box autofill-footer">
<div class="autofill-footer-row autofill-warning"></div>
<div class="autofill-footer-row autofill-button"></div>
</div>
`;
}
constructor() {
super();
this.addEventListener("click", event => {
if (event.button != 0) {
return;
}
if (this._warningTextBox.contains(event.originalTarget)) {
return;
}
window.openPreferences("privacy-form-autofill");
});
}
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this.textContent = "";
this.appendChild(this.constructor.fragment);
this._itemBox = this.querySelector(".autofill-footer");
this._optionButton = this.querySelector(".autofill-button");
this._warningTextBox = this.querySelector(".autofill-warning");
/**
* A handler for updating warning message once selectedIndex has been changed.
*
* There're three different states of warning message:
* 1. None of addresses were selected: We show all the categories intersection of fields in the
* form and fields in the results.
* 2. An address was selested: Show the additional categories that will also be filled.
* 3. An address was selected, but the focused category is the same as the only one category: Only show
* the exact category that we're going to fill in.
*
* @private
* @param {object} data
* Message data
* @param {string[]} data.categories
* The categories of all the fields contained in the selected address.
*/
this.updateWarningNote = data => {
let categories =
data && data.categories ? data.categories : this._allFieldCategories;
// If the length of categories is 1, that means all the fillable fields are in the same
// category. We will change the way to inform user according to this flag. When the value
// is true, we show "Also autofills ...", otherwise, show "Autofills ..." only.
let hasExtraCategories = categories.length > 1;
// Show the categories in certain order to conform with the spec.
let orderedCategoryList = [
{ id: "address", l10nId: "category.address" },
{ id: "name", l10nId: "category.name" },
{ id: "organization", l10nId: "category.organization2" },
{ id: "tel", l10nId: "category.tel" },
{ id: "email", l10nId: "category.email" },
];
let showCategories = hasExtraCategories
? orderedCategoryList.filter(
category =>
categories.includes(category.id) &&
category.id != this._focusedCategory
)
: [
orderedCategoryList.find(
category => category.id == this._focusedCategory
),
];
let separator =
this._stringBundle.GetStringFromName("fieldNameSeparator");
let warningTextTmplKey = hasExtraCategories
? "phishingWarningMessage"
: "phishingWarningMessage2";
let categoriesText = showCategories
.map(category =>
this._stringBundle.GetStringFromName(category.l10nId)
)
.join(separator);
this._warningTextBox.textContent =
this._stringBundle.formatStringFromName(warningTextTmplKey, [
categoriesText,
]);
this.parentNode.parentNode.adjustHeight();
};
this._adjustAcItem();
}
_onCollapse() {
if (this.showWarningText) {
let { FormAutofillParent } = ChromeUtils.importESModule(
"resource://autofill/FormAutofillParent.sys.mjs"
);
FormAutofillParent.removeMessageObserver(this);
}
this._itemBox.removeAttribute("no-warning");
}
_adjustAcItem() {
this._adjustAutofillItemLayout();
this.setAttribute("formautofillattached", "true");
let value = JSON.parse(this.getAttribute("ac-value"));
this._allFieldCategories = value.categories;
this._focusedCategory = value.focusedCategory;
this.showWarningText = this._allFieldCategories && this._focusedCategory;
if (this.showWarningText) {
let { FormAutofillParent } = ChromeUtils.importESModule(
"resource://autofill/FormAutofillParent.sys.mjs"
);
FormAutofillParent.addMessageObserver(this);
this.updateWarningNote();
} else {
this._itemBox.setAttribute("no-warning", "true");
}
}
}
customElements.define(
"autocomplete-profile-listitem-footer",
MozAutocompleteProfileListitemFooter,
{ extends: "richlistitem" }
);
class MozAutocompleteCreditcardInsecureField extends MozAutocompleteProfileListitemBase {
static get markup() {
return `

View file

@ -13,7 +13,6 @@
> richlistbox > richlistitem {
&[originaltype="autofill-profile"],
&[originaltype="autofill-footer"],
&[originaltype="autofill-insecureWarning"] {
display: block;
margin: 0;

View file

@ -2,21 +2,6 @@
# 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/.
# LOCALIZATION NOTE (category.address, category.name, category.organization2, category.tel, category.email):
# Used in autofill drop down suggestion to indicate what other categories Form Autofill will attempt to fill.
category.address = address
category.name = name
category.organization2 = organization
category.tel = phone
category.email = email
# LOCALIZATION NOTE (fieldNameSeparator): This is used as a separator between categories.
fieldNameSeparator = ,\u0020
# LOCALIZATION NOTE (phishingWarningMessage, phishingWarningMessage2): The warning
# text that is displayed for informing users what categories are about to be filled.
# "%S" will be replaced with a list generated from the pre-defined categories.
# The text would be e.g. Also autofills organization, phone, email.
phishingWarningMessage = Also autofills %S
phishingWarningMessage2 = Autofills %S
# LOCALIZATION NOTE (insecureFieldWarningDescription): %S is brandShortName. This string is used in drop down
# suggestion when users try to autofill credit card on an insecure website (without https).
insecureFieldWarningDescription = %S has detected an insecure site. Form Autofill is temporarily disabled.

View file

@ -11,11 +11,6 @@ xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-i
color: SelectedItemText;
}
xul|richlistitem[originaltype="autofill-footer"][selected="true"] > .autofill-item-box > .autofill-button,
xul|richlistitem[originaltype="autofill-clear-button"][selected="true"] > .autofill-item-box > .autofill-button {
background-color: ButtonHighlight;
}
xul|richlistitem[originaltype="autofill-insecureWarning"] {
border-bottom: 1px solid var(--panel-separator-color);
background-color: var(--arrowpanel-dimmed);
@ -27,14 +22,10 @@ xul|richlistitem[originaltype="autofill-insecureWarning"] {
--col-spacer: 7px;
--item-width: calc(50% - (var(--col-spacer) / 2));
--comment-text-color: GreyText;
--warning-text-color: GreyText;
--warning-background-color: rgba(248, 232, 28, .2);
--default-font-size: 12;
--label-font-size: 12;
--comment-font-size: 10;
--warning-font-size: 10;
--btn-font-size: 11;
}
.autofill-item-box[size="small"] {
@ -49,13 +40,6 @@ xul|richlistitem[originaltype="autofill-insecureWarning"] {
--comment-font-size: 11;
}
.autofill-footer,
.autofill-footer[size="small"] {
--item-width: 100%;
--item-padding-vertical: 0;
--item-padding-horizontal: 0;
}
.autofill-item-box {
box-sizing: border-box;
margin: 0;
@ -125,41 +109,6 @@ xul|richlistitem[originaltype="autofill-insecureWarning"] {
text-align: start;
}
.autofill-footer {
padding: 0;
flex-direction: column;
}
.autofill-footer > .autofill-footer-row {
display: flex;
justify-content: center;
align-items: center;
width: var(--item-width);
}
.autofill-footer > .autofill-warning {
padding: 2.5px 0;
color: var(--warning-text-color);
text-align: center;
background-color: var(--warning-background-color);
border-bottom: 1px solid rgba(38,38,38,.15);
font-size: calc(var(--warning-font-size) / var(--default-font-size) * 1em);
}
.autofill-footer > .autofill-button {
box-sizing: border-box;
padding: 0 10px;
min-height: 40px;
background-color: ButtonFace;
font-size: calc(var(--btn-font-size) / var(--default-font-size) * 1em);
color: ButtonText;
text-align: center;
}
.autofill-footer[no-warning="true"] > .autofill-warning {
display: none;
}
.autofill-insecure-item {
box-sizing: border-box;
padding: 4px 0;

View file

@ -9,10 +9,6 @@
--default-font-size: 12;
}
xul|richlistitem[originaltype="autofill-footer"][selected="true"] > .autofill-item-box > .autofill-button {
background-color: color-mix(in srgb, Field 90%, FieldText);
}
@media (prefers-contrast) {
xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box {
background-color: SelectedItem;

View file

@ -70,6 +70,7 @@ add_task(async function test_press_enter_on_footer() {
} = browser;
await openPopupOn(browser, "#organization");
// Navigate to the footer and press enter.
const listItemElems = itemsBox.querySelectorAll(
".autocomplete-richlistitem"
@ -80,7 +81,7 @@ add_task(async function test_press_enter_on_footer() {
true
);
for (let i = 0; i < listItemElems.length; i++) {
if (!listItemElems[i].collapsed) {
if (!listItemElems[i].disabled) {
await BrowserTestUtils.synthesizeKey("VK_DOWN", {}, browser);
}
}
@ -115,7 +116,6 @@ add_task(async function test_click_on_footer() {
while (optionButton.collapsed) {
optionButton = optionButton.previousElementSibling;
}
optionButton = optionButton._optionButton;
const prefTabPromise = BrowserTestUtils.waitForNewTab(
gBrowser,
@ -145,15 +145,7 @@ add_task(async function test_phishing_warning_single_category() {
await BrowserTestUtils.withNewTab(
{ gBrowser, url: URL },
async function (browser) {
const {
autoCompletePopup: { richlistbox: itemsBox },
} = browser;
await openPopupOn(browser, "#tel");
const warningBox = itemsBox.querySelector(
".autocomplete-richlistitem:last-child"
)._warningTextBox;
ok(warningBox, "Got phishing warning box");
await expectWarningText(browser, "Also autofills address");
await closePopup(browser);
}

View file

@ -56,21 +56,21 @@ add_task(async function test_insecure_form() {
protocol: "https",
focusInput: "#organization",
expectedType: "autofill-profile",
expectedResultLength: 2,
expectedResultLength: 3, // add one for the status row
},
{
urlPath: TEST_URL_PATH,
protocol: "http",
focusInput: "#organization",
expectedType: "autofill-profile",
expectedResultLength: 2,
expectedResultLength: 3, // add one for the status row
},
{
urlPath: TEST_URL_PATH_CC,
protocol: "https",
focusInput: "#cc-name",
expectedType: "autofill-profile",
expectedResultLength: 3,
expectedResultLength: 3, // no status row here
},
{
urlPath: TEST_URL_PATH_CC,

View file

@ -535,25 +535,6 @@ async function runAndWaitForAutocompletePopupOpen(browser, taskFn) {
await taskFn();
await popupShown;
await BrowserTestUtils.waitForMutationCondition(
browser.autoCompletePopup.richlistbox,
{ childList: true, subtree: true, attributes: true },
() => {
const listItemElems = getDisplayedPopupItems(browser);
return (
!![...listItemElems].length &&
[...listItemElems].every(item => {
return (
(item.getAttribute("originaltype") == "autofill-profile" ||
item.getAttribute("originaltype") == "autofill-insecureWarning" ||
item.getAttribute("originaltype") == "autofill-clear-button" ||
item.getAttribute("originaltype") == "autofill-footer") &&
item.hasAttribute("formautofillattached")
);
})
);
}
);
}
async function waitForPopupEnabled(browser) {
@ -850,14 +831,8 @@ async function expectWarningText(browser, expectedText) {
const {
autoCompletePopup: { richlistbox: itemsBox },
} = browser;
let warningBox = itemsBox.querySelector(
".autocomplete-richlistitem:last-child"
);
while (warningBox.collapsed) {
warningBox = warningBox.previousSibling;
}
warningBox = warningBox._warningTextBox;
let warningBox = itemsBox.querySelector(".ac-status");
ok(warningBox.parentNode.disabled, "Got warning box and is disabled");
await BrowserTestUtils.waitForMutationCondition(
warningBox,

View file

@ -270,12 +270,9 @@ async function onStorageChanged(type) {
});
}
function checkMenuEntries(expectedValues, isFormAutofillResult = true) {
function checkMenuEntries(expectedValues, extraRows = 1) {
let actualValues = getMenuEntries();
// Expect one more item would appear at the bottom as the footer if the result is from form autofill.
let expectedLength = isFormAutofillResult
? expectedValues.length + 1
: expectedValues.length;
let expectedLength = expectedValues.length + extraRows;
is(actualValues.length, expectedLength, " Checking length of expected menu");
for (let i = 0; i < expectedValues.length; i++) {

View file

@ -31,6 +31,8 @@ let MOCK_STORAGE = [{
initPopupListener();
let statusText = '"Also autofills address, name, organization"';
add_task(async function setupStorage() {
await addAddress(MOCK_STORAGE[0]);
@ -46,9 +48,9 @@ add_task(async function check_switch_autofill_form_popup() {
await expectPopup();
checkMenuEntries(
[
`{"primary":"+13453453456","secondary":"123 Sesame Street."}`,
`{"primary":"+13453453456","secondary":"123 Sesame Street.","status":${statusText}}`,
`{"primary":"","secondary":"","status":${statusText},"style":"status"}`,
],
true
);
await testMenuEntry(0, "!(el instanceof MozElements.MozAutocompleteRichlistitem)");
@ -60,7 +62,7 @@ add_task(async function check_switch_oridnal_form_popup() {
await setInput("#username", "");
synthesizeKey("KEY_ArrowDown");
await expectPopup();
checkMenuEntries(["petya"], false);
checkMenuEntries(["petya"], 0);
await testMenuEntry(0, "el instanceof MozElements.MozAutocompleteRichlistitem");
});
@ -73,9 +75,9 @@ add_task(async function check_switch_autofill_form_popup_back() {
await expectPopup();
checkMenuEntries(
[
`{"primary":"+13453453456","secondary":"123 Sesame Street."}`,
`{"primary":"+13453453456","secondary":"123 Sesame Street.","status":${statusText}}`,
`{"primary":"","secondary":"","status":${statusText},"style":"status"}`,
],
true
);
await testMenuEntry(0, "!(el instanceof MozElements.MozAutocompleteRichlistitem)");

View file

@ -39,8 +39,12 @@ add_task(async function check_autocomplete_on_autofocus_field() {
synthesizeKey("KEY_ArrowDown");
await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(address =>
JSON.stringify({primary: address.organization, secondary: address["street-address"]})
));
JSON.stringify({
primary: address.organization,
secondary: address["street-address"],
status: "Also autofills address, phone"
})
), 2);
});
</script>

View file

@ -84,8 +84,9 @@ add_task(async function check_menu_when_both_existed() {
JSON.stringify({
primary: address.organization,
secondary: FormAutofillUtils.toOneLineAddress(address["street-address"]),
status: "Also autofills address, phone"
})
));
), 2);
await setInput("#street-address", "");
await notExpectPopup();
@ -95,8 +96,9 @@ add_task(async function check_menu_when_both_existed() {
JSON.stringify({
primary: FormAutofillUtils.toOneLineAddress(address["street-address"]),
secondary: address.organization,
status: "Also autofills organization, phone"
})
));
), 2);
await setInput("#tel", "");
await notExpectPopup();
@ -106,8 +108,9 @@ add_task(async function check_menu_when_both_existed() {
JSON.stringify({
primary: address.tel,
secondary: FormAutofillUtils.toOneLineAddress(address["street-address"]),
status: "Also autofills address, organization"
})
));
), 2);
await setInput("#address-line1", "");
await notExpectPopup();
@ -117,8 +120,9 @@ add_task(async function check_menu_when_both_existed() {
JSON.stringify({
primary: FormAutofillUtils.toOneLineAddress(address["street-address"]),
secondary: address.organization,
status: "Also autofills organization, phone"
})
));
), 2);
});
// Display history search result if no matched data in addresses.
@ -155,8 +159,9 @@ add_task(async function check_fields_after_form_autofill() {
JSON.stringify({
primary: address.organization,
secondary: FormAutofillUtils.toOneLineAddress(address["street-address"]),
status: "Also autofills address, phone"
})
).slice(1));
).slice(1), 2);
synthesizeKey("KEY_ArrowDown");
await triggerAutofillAndCheckProfile(MOCK_STORAGE[1]);
synthesizeKey("KEY_Escape");
@ -183,8 +188,9 @@ add_task(async function check_form_autofill_resume() {
JSON.stringify({
primary: address.tel,
secondary: FormAutofillUtils.toOneLineAddress(address["street-address"]),
status: "Also autofills address, organization"
})
));
), 2);
await triggerAutofillAndCheckProfile(MOCK_STORAGE[0]);
});

View file

@ -61,8 +61,12 @@ async function checkFormChangeHappened(formId) {
await expectPopup();
synthesizeKey("KEY_ArrowDown");
checkMenuEntries(MOCK_STORAGE.map(address =>
JSON.stringify({primary: address.tel, secondary: address.name})
));
JSON.stringify({
primary: address.tel,
secondary: address.name,
status: "Also autofills name, organization"
})
), 2);
// Click the first entry of the autocomplete popup and make sure all fields are autofilled
synthesizeKey("KEY_Enter");
@ -76,8 +80,9 @@ async function checkFormChangeHappened(formId) {
// Click on an autofilled field would show an autocomplete popup with "clear form" entry
checkMenuEntries([
JSON.stringify({primary: "", secondary: ""}), // Clear Autofill Form
], true);
"Clear Autofill Form", // Clear Autofill Form
"Manage addresses" // FormAutofill Preferemce
], 0);
// This is for checking the changes of element removed and added then.
document.querySelector(`#${formId} input[name=address-level2]`).remove();
@ -87,8 +92,12 @@ async function checkFormChangeHappened(formId) {
synthesizeKey("KEY_ArrowDown");
await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(address =>
JSON.stringify({primary: address["address-level2"], secondary: address.name})
));
JSON.stringify({
primary: address["address-level2"],
secondary: address.name,
status: "Also autofills name, organization, phone"
})
), 2);
// Make sure everything is autofilled in the end
synthesizeKey("KEY_ArrowDown");

View file

@ -81,7 +81,7 @@ add_task(async function check_preview() {
// Navigate to the footer
synthesizeKey("KEY_ArrowDown");
await notifySelectedIndex(MOCK_STORAGE.length);
await notifySelectedIndex(MOCK_STORAGE.length + 1); // skip over the status row
await checkFormFieldsStyle(null);
synthesizeKey("KEY_ArrowDown");

View file

@ -70,6 +70,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "Sesame Street",
secondary: "123 Sesame Street.",
status: "Also autofills address, name, phone",
}),
image: "",
},
@ -80,6 +81,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "Mozilla",
secondary: "331 E. Evelyn Avenue",
status: "Also autofills address, name, phone",
}),
image: "",
},
@ -104,6 +106,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "1-345-345-3456.",
secondary: "123 Sesame Street.",
status: "Also autofills address, name, organization",
}),
image: "",
},
@ -114,6 +117,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "1-650-903-0800",
secondary: "331 E. Evelyn Avenue",
status: "Also autofills address, name, organization",
}),
image: "",
},
@ -124,6 +128,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "1-000-000-0000",
secondary: "321, No Name St. 2nd line 3rd line",
status: "Also autofills address",
}),
image: "",
},
@ -148,6 +153,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "123 Sesame Street.",
secondary: "Timothy Berners-Lee",
status: "Also autofills name, organization, phone",
}),
image: "",
},
@ -158,6 +164,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "331 E. Evelyn Avenue",
secondary: "John Doe",
status: "Also autofills name, organization, phone",
}),
image: "",
},
@ -168,6 +175,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "321, No Name St. 2nd line 3rd line",
secondary: "1-000-000-0000",
status: "Also autofills phone",
}),
image: "",
},
@ -192,6 +200,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "123 Sesame Street.",
secondary: "Timothy Berners-Lee",
status: "Also autofills name, organization, phone",
}),
image: "",
},
@ -202,6 +211,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "331 E. Evelyn Avenue",
secondary: "John Doe",
status: "Also autofills name, organization, phone",
}),
image: "",
},
@ -212,6 +222,7 @@ let addressTestCases = [
label: JSON.stringify({
primary: "321, No Name St.",
secondary: "1-000-000-0000",
status: "Also autofills phone",
}),
image: "",
},
@ -419,6 +430,13 @@ add_task(async function test_all_patterns() {
if (actual.getStyleAt(actual.matchCount - 1) == "action") {
expectedItemLength++;
}
// Add one row for the status.
if (
actual.matchCount > 2 &&
actual.getStyleAt(actual.matchCount - 2) == "status"
) {
expectedItemLength++;
}
equal(actual.searchResult, expectedValue.searchResult);
equal(actual.defaultIndex, expectedValue.defaultIndex);

View file

@ -164,6 +164,20 @@
text-align: center;
}
/* status items */
> .ac-status {
padding: var(--space-xsmall) var(--space-small);
text-align: center;
background-color: var(--color-background-information);
width: 100%;
border-bottom: 1px solid rgba(38,38,38,.15);
font-size: calc(10 / 12 * 1em);
}
&:has(> .ac-status) {
opacity: 1;
}
/* Insecure field warning */
&[originaltype="insecureWarning"] {
background-color: var(--arrowpanel-dimmed);

View file

@ -2,7 +2,8 @@
# http://creativecommons.org/publicdomain/zero/1.0/
import fluent.syntax.ast as FTL
from fluent.migrate.transforms import COPY
from fluent.migrate.helpers import VARIABLE_REFERENCE
from fluent.migrate.transforms import COPY, REPLACE
def migrate(ctx):
@ -18,5 +19,45 @@ def migrate(ctx):
id=FTL.Identifier("autofill-clear-form-label"),
value=COPY(propertiesSource, "clearFormBtnLabel2"),
),
FTL.Message(
id=FTL.Identifier("autofill-category-address"),
value=COPY(propertiesSource, "category.address"),
),
FTL.Message(
id=FTL.Identifier("autofill-category-name"),
value=COPY(propertiesSource, "category.name"),
),
FTL.Message(
id=FTL.Identifier("autofill-category-organization"),
value=COPY(propertiesSource, "category.organization2"),
),
FTL.Message(
id=FTL.Identifier("autofill-category-tel"),
value=COPY(propertiesSource, "category.tel"),
),
FTL.Message(
id=FTL.Identifier("autofill-category-email"),
value=COPY(propertiesSource, "category.email"),
),
FTL.Message(
id=FTL.Identifier("autofill-phishing-warningmessage-extracategory"),
value=REPLACE(
propertiesSource,
"phishingWarningMessage",
{
"%1$S": VARIABLE_REFERENCE("categories"),
},
),
),
FTL.Message(
id=FTL.Identifier("autofill-phishing-warningmessage"),
value=REPLACE(
propertiesSource,
"phishingWarningMessage2",
{
"%1$S": VARIABLE_REFERENCE("categories"),
},
),
),
],
)

View file

@ -671,29 +671,8 @@ export class FormAutofillChild extends JSWindowActorChild {
!lastAutoCompleteResult ||
lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile"
) {
this.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {});
lazy.ProfileAutocomplete._clearProfilePreview();
} else {
let focusedInputDetails = this.activeFieldDetail;
let profile = JSON.parse(
lastAutoCompleteResult.getCommentAt(selectedIndex)
);
let allFieldNames = this.activeSection.allFieldNames;
let profileFields = allFieldNames.filter(
fieldName => !!profile[fieldName]
);
let focusedCategory = lazy.FormAutofillUtils.getCategoryFromFieldName(
focusedInputDetails.fieldName
);
let categories =
lazy.FormAutofillUtils.getCategoriesFromFieldNames(profileFields);
this.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {
focusedCategory,
categories,
});
lazy.ProfileAutocomplete._previewSelectedProfile(selectedIndex);
}
}

View file

@ -318,10 +318,6 @@ export class AddressResult extends ProfileAutoCompleteResult {
let footerItem = {
primary: manageLabel,
secondary: "",
categories:
lazy.FormAutofillUtils.getCategoriesFromFieldNames(allFieldNames),
focusedCategory:
lazy.FormAutofillUtils.getCategoryFromFieldName(focusedFieldName),
};
if (this._isInputAutofilled) {
@ -336,6 +332,9 @@ export class AddressResult extends ProfileAutoCompleteResult {
return labels;
}
let focusedCategory =
lazy.FormAutofillUtils.getCategoryFromFieldName(focusedFieldName);
// Skip results without a primary label.
let labels = profiles
.filter(profile => {
@ -349,6 +348,13 @@ export class AddressResult extends ProfileAutoCompleteResult {
) {
primaryLabel = profile["-moz-street-address-one-line"];
}
let profileFields = allFieldNames.filter(
fieldName => !!profile[fieldName]
);
let categories =
lazy.FormAutofillUtils.getCategoriesFromFieldNames(profileFields);
return {
primary: primaryLabel,
secondary: this._getSecondaryLabel(
@ -356,13 +362,68 @@ export class AddressResult extends ProfileAutoCompleteResult {
allFieldNames,
profile
),
status: this.getStatusNote(categories, focusedCategory),
};
});
let allCategories =
lazy.FormAutofillUtils.getCategoriesFromFieldNames(allFieldNames);
if (allCategories && allCategories.length) {
let statusItem = {
primary: "",
secondary: "",
status: this.getStatusNote(allCategories, focusedCategory),
style: "status",
};
labels.push(statusItem);
}
labels.push(footerItem);
return labels;
}
getStatusNote(categories, focusedCategory) {
if (!categories || !categories.length) {
return "";
}
// If the length of categories is 1, that means all the fillable fields are in the same
// category. We will change the way to inform user according to this flag. When the value
// is true, we show "Also autofills ...", otherwise, show "Autofills ..." only.
let hasExtraCategories = categories.length > 1;
// Show the categories in certain order to conform with the spec.
let orderedCategoryList = [
"address",
"name",
"organization",
"tel",
"email",
];
let showCategories = hasExtraCategories
? orderedCategoryList.filter(
category =>
categories.includes(category) && category != focusedCategory
)
: [orderedCategoryList.find(category => category == focusedCategory)];
let formatter = new Intl.ListFormat(undefined, {
style: "narrow",
});
let categoriesText = showCategories.map(category =>
lazy.l10n.formatValueSync("autofill-category-" + category)
);
categoriesText = formatter.format(categoriesText);
let statusTextTmplKey = hasExtraCategories
? "autofill-phishing-warningmessage-extracategory"
: "autofill-phishing-warningmessage";
return lazy.l10n.formatValueSync(statusTextTmplKey, {
categories: categoriesText,
});
}
}
export class CreditCardResult extends ProfileAutoCompleteResult {

View file

@ -321,12 +321,7 @@
_collapseUnusedItems() {
let existingItemsCount = this.richlistbox.children.length;
for (let i = this.matchCount; i < existingItemsCount; ++i) {
let item = this.richlistbox.children[i];
item.collapsed = true;
if (typeof item._onCollapse == "function") {
item._onCollapse();
}
this.richlistbox.children[i].collapsed = true;
}
}
@ -417,9 +412,9 @@
// _adjustAcItem() are unreusable.
const UNREUSEABLE_STYLES = [
"autofill-profile",
"autofill-footer",
"autofill-insecureWarning",
"action",
"status",
"generatedPassword",
"generic",
"importableLearnMore",
@ -445,15 +440,15 @@
case "autofill-profile":
options = { is: "autocomplete-profile-listitem" };
break;
case "autofill-footer":
options = { is: "autocomplete-profile-listitem-footer" };
break;
case "autofill-insecureWarning":
options = { is: "autocomplete-creditcard-insecure-field" };
break;
case "action":
options = { is: "autocomplete-action-richlistitem" };
break;
case "status":
options = { is: "autocomplete-status-richlistitem" };
break;
case "generic":
options = { is: "autocomplete-two-line-richlistitem" };
break;

View file

@ -730,6 +730,53 @@
}
}
// A row that conveys status information assigned from the status field
// within the comment associated with the selected item in the list.
class MozAutocompleteStatusRichlistitem extends MozAutocompleteTwoLineRichlistitem {
static get markup() {
return `<div class="ac-status" xmlns="http://www.w3.org/1999/xhtml"></div>`;
}
connectedCallback() {
super.connectedCallback();
this.parentNode.addEventListener("select", this);
this.eventListenerParentNode = this.parentNode;
}
disconnectedCallback() {
this.eventListenerParentNode?.removeEventListener("select", this);
this.eventListenerParentNode = null;
}
handleEvent(event) {
if (event.type == "select") {
let selectedItem = event.target.selectedItem;
if (selectedItem) {
this.#setStatus(selectedItem);
}
}
}
#setStatus(item) {
// For normal rows, use that row's comment, otherwise use the status's
// comment which serves as the default label.
let target =
!item || item instanceof MozAutocompleteActionRichlistitem
? this
: item;
let comment = JSON.parse(target.getAttribute("ac-comment"));
let statusBox = this.querySelector(".ac-status");
statusBox.textContent = comment?.status || "";
}
_adjustAcItem() {
super._adjustAcItem();
this.#setStatus(this);
this.setAttribute("disabled", "true");
}
}
class MozAutocompleteGeneratedPasswordRichlistitem extends MozAutocompleteTwoLineRichlistitem {
constructor() {
super();
@ -870,6 +917,14 @@
}
);
customElements.define(
"autocomplete-status-richlistitem",
MozAutocompleteStatusRichlistitem,
{
extends: "richlistitem",
}
);
customElements.define(
"autocomplete-generated-password-richlistitem",
MozAutocompleteGeneratedPasswordRichlistitem,

View file

@ -73,3 +73,22 @@ autofill-card-network-mastercard = MasterCard
autofill-card-network-mir = MIR
autofill-card-network-unionpay = Union Pay
autofill-card-network-visa = Visa
# The warning text that is displayed for informing users what categories are
# about to be filled. The text would be, for example,
# Also autofills organization, phone, email.
# Variables:
# $categories - one or more of the categories, see autofill-category-X below
autofill-phishing-warningmessage-extracategory = Also autofills { $categories }
# Variation when all are in the same category.
# Variables:
# $categories - one or more of the categories
autofill-phishing-warningmessage = Autofills { $categories }
# Used in autofill drop down suggestion to indicate what other categories Form Autofill will attempt to fill.
autofill-category-address = address
autofill-category-name = name
autofill-category-organization = organization
autofill-category-tel = phone
autofill-category-email = email