forked from mirrors/gecko-dev
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:
parent
1e83118947
commit
e94e76f935
22 changed files with 268 additions and 312 deletions
|
|
@ -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 `
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
|
||||
> richlistbox > richlistitem {
|
||||
&[originaltype="autofill-profile"],
|
||||
&[originaltype="autofill-footer"],
|
||||
&[originaltype="autofill-insecureWarning"] {
|
||||
display: block;
|
||||
margin: 0;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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++) {
|
||||
|
|
|
|||
|
|
@ -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)");
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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]);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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"),
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue