Bug 1883549, remove formautofill autocomplete item components and create one based on MozAutocompleteTwoLineRichlistitem, r=credential-management-reviewers,sgalich,geckoview-reviewers,owlish

Differential Revision: https://phabricator.services.mozilla.com/D203612
This commit is contained in:
Neil Deakin 2024-03-29 01:23:25 +00:00
parent 65ed3e5559
commit 6052b367ff
24 changed files with 138 additions and 450 deletions

View File

@ -13,12 +13,6 @@ let ignoreList = [
// CodeMirror is imported as-is, see bug 1004423. // CodeMirror is imported as-is, see bug 1004423.
{ sourceName: /codemirror\.css$/i, isFromDevTools: true }, { sourceName: /codemirror\.css$/i, isFromDevTools: true },
// UA-only media features. // UA-only media features.
{
sourceName: /\b(autocomplete-item)\.css$/,
errorMessage: /Expected media feature name but found \u2018-moz.*/i,
isFromDevTools: false,
platforms: ["windows"],
},
{ {
sourceName: sourceName:
/\b(contenteditable|EditorOverride|svg|forms|html|mathml|ua)\.css$/i, /\b(contenteditable|EditorOverride|svg|forms|html|mathml|ua)\.css$/i,
@ -27,7 +21,7 @@ let ignoreList = [
}, },
{ {
sourceName: sourceName:
/\b(scrollbars|xul|html|mathml|ua|forms|svg|manageDialog|autocomplete-item-shared|formautofill)\.css$/i, /\b(scrollbars|xul|html|mathml|ua|forms|svg|manageDialog|formautofill)\.css$/i,
errorMessage: /Unknown property.*-moz-/i, errorMessage: /Unknown property.*-moz-/i,
isFromDevTools: false, isFromDevTools: false,
}, },

View File

@ -48,14 +48,6 @@ function ensureCssLoaded(domWindow) {
} }
insertStyleSheet(domWindow, "chrome://formautofill/content/formautofill.css"); insertStyleSheet(domWindow, "chrome://formautofill/content/formautofill.css");
insertStyleSheet(
domWindow,
"chrome://formautofill/content/skin/autocomplete-item-shared.css"
);
insertStyleSheet(
domWindow,
"chrome://formautofill/content/skin/autocomplete-item.css"
);
} }
this.formautofill = class extends ExtensionAPI { this.formautofill = class extends ExtensionAPI {

View File

@ -1,157 +0,0 @@
/* 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/. */
// This file is loaded into the browser window scope.
/* eslint-env mozilla/browser-window */
/* eslint-disable mozilla/balanced-listeners */ // Not relevant since the document gets unloaded.
"use strict";
// Wrap in a block to prevent leaking to window scope.
(() => {
function sendMessageToBrowser(msgName, data) {
let { AutoCompleteParent } = ChromeUtils.importESModule(
"resource://gre/actors/AutoCompleteParent.sys.mjs"
);
let actor = AutoCompleteParent.getCurrentActor();
if (!actor) {
return;
}
actor.manager.getActor("FormAutofill").sendAsyncMessage(msgName, data);
}
class MozAutocompleteProfileListitemBase extends MozElements.MozRichlistitem {
constructor() {
super();
/**
* For form autofill, we want to unify the selection no matter by
* keyboard navigation or mouseover in order not to confuse user which
* profile preview is being shown. This field is set to true to indicate
* that selectedIndex of popup should be changed while mouseover item
*/
this.selectedByMouseOver = true;
}
get _stringBundle() {
if (!this.__stringBundle) {
this.__stringBundle = Services.strings.createBundle(
"chrome://formautofill/locale/formautofill.properties"
);
}
return this.__stringBundle;
}
_cleanup() {
this.removeAttribute("formautofillattached");
if (this._itemBox) {
this._itemBox.removeAttribute("size");
}
}
_onOverflow() {}
_onUnderflow() {}
handleOverUnderflow() {}
_adjustAutofillItemLayout() {
let outerBoxRect = this.parentNode.getBoundingClientRect();
// Make item fit in popup as XUL box could not constrain
// item's width
this._itemBox.style.width = outerBoxRect.width + "px";
// Use two-lines layout when width is smaller than 150px or
// 185px if an image precedes the label.
let oneLineMinRequiredWidth = this.getAttribute("ac-image") ? 185 : 150;
if (outerBoxRect.width <= oneLineMinRequiredWidth) {
this._itemBox.setAttribute("size", "small");
} else {
this._itemBox.removeAttribute("size");
}
}
}
MozElements.MozAutocompleteProfileListitem = class MozAutocompleteProfileListitem extends (
MozAutocompleteProfileListitemBase
) {
static get markup() {
return `
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box">
<div class="profile-label-col profile-item-col">
<span class="profile-label"></span>
</div>
<div class="profile-comment-col profile-item-col">
<span class="profile-comment"></span>
</div>
</div>
`;
}
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this.textContent = "";
this.appendChild(this.constructor.fragment);
this._itemBox = this.querySelector(".autofill-item-box");
this._label = this.querySelector(".profile-label");
this._comment = this.querySelector(".profile-comment");
this.initializeAttributeInheritance();
this._adjustAcItem();
}
static get inheritedAttributes() {
return {
".autofill-item-box": "ac-image",
};
}
set selected(val) {
if (val) {
this.setAttribute("selected", "true");
} else {
this.removeAttribute("selected");
}
sendMessageToBrowser("FormAutofill:PreviewProfile");
}
get selected() {
return this.getAttribute("selected") == "true";
}
_adjustAcItem() {
this._adjustAutofillItemLayout();
this.setAttribute("formautofillattached", "true");
this._itemBox.style.setProperty(
"--primary-icon",
`url(${this.getAttribute("ac-image")})`
);
let { primary, secondary, ariaLabel } = JSON.parse(
this.getAttribute("ac-value")
);
this._label.textContent = primary.toString().replaceAll("*", "•");
this._comment.textContent = secondary.toString().replaceAll("*", "•");
if (ariaLabel) {
this.setAttribute("aria-label", ariaLabel);
}
}
};
customElements.define(
"autocomplete-profile-listitem",
MozElements.MozAutocompleteProfileListitem,
{ extends: "richlistitem" }
);
})();

View File

@ -3,12 +3,12 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#PopupAutoComplete { #PopupAutoComplete {
&[resultstyles~="autofill-profile"] { &[resultstyles~="autofill"] {
min-width: 150px !important; min-width: 150px !important;
} }
> richlistbox > richlistitem { > richlistbox > richlistitem {
&[originaltype="autofill-profile"] { &[originaltype="autofill"] {
display: block; display: block;
margin: 0; margin: 0;
padding: 0; padding: 0;

View File

@ -18,17 +18,14 @@ FINAL_TARGET_FILES.features["formautofill@mozilla.org"] += [
if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
FINAL_TARGET_FILES.features["formautofill@mozilla.org"].chrome.content.skin += [ FINAL_TARGET_FILES.features["formautofill@mozilla.org"].chrome.content.skin += [
"skin/linux/autocomplete-item.css",
"skin/linux/editDialog.css", "skin/linux/editDialog.css",
] ]
elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
FINAL_TARGET_FILES.features["formautofill@mozilla.org"].chrome.content.skin += [ FINAL_TARGET_FILES.features["formautofill@mozilla.org"].chrome.content.skin += [
"skin/osx/autocomplete-item.css",
"skin/osx/editDialog.css", "skin/osx/editDialog.css",
] ]
elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
FINAL_TARGET_FILES.features["formautofill@mozilla.org"].chrome.content.skin += [ FINAL_TARGET_FILES.features["formautofill@mozilla.org"].chrome.content.skin += [
"skin/windows/autocomplete-item.css",
"skin/windows/editDialog.css", "skin/windows/editDialog.css",
] ]

View File

@ -1,10 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@namespace url("http://www.w3.org/1999/xhtml");
.autofill-item-box {
--default-font-size: 14.25;
}

View File

@ -1,9 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@namespace url("http://www.w3.org/1999/xhtml");
.autofill-item-box {
--default-font-size: 11;
}

View File

@ -1,105 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@namespace url("http://www.w3.org/1999/xhtml");
@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box {
background-color: SelectedItem;
color: SelectedItemText;
}
.autofill-item-box {
--item-padding-vertical: 7px;
--item-padding-horizontal: 10px;
--col-spacer: 7px;
--item-width: calc(50% - (var(--col-spacer) / 2));
--comment-text-color: GreyText;
--default-font-size: 12;
--label-font-size: 12;
--comment-font-size: 10;
}
.autofill-item-box[size="small"] {
--item-padding-vertical: 7px;
--col-spacer: 0px;
--row-spacer: 3px;
--item-width: 100%;
}
.autofill-item-box:not([ac-image=""]) {
--item-padding-vertical: 6.5px;
--comment-font-size: 11;
}
.autofill-item-box {
box-sizing: border-box;
margin: 0;
border-bottom: 1px solid rgba(38,38,38,.15);
padding: var(--item-padding-vertical) 0;
padding-inline: var(--item-padding-horizontal);
display: flex;
flex-direction: row;
flex-wrap: wrap;
align-items: center;
background-color: Field;
color: FieldText;
}
.autofill-item-box:last-child {
border-bottom: 0;
}
.autofill-item-box > .profile-item-col {
box-sizing: border-box;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: var(--item-width);
}
.autofill-item-box > .profile-label-col {
text-align: start;
}
.autofill-item-box:not([ac-image=""]) > .profile-label-col::before {
margin-inline-end: 5px;
float: inline-start;
content: "";
width: 16px;
height: 16px;
background-image: var(--primary-icon);
background-size: contain;
background-repeat: no-repeat;
background-position: center;
-moz-context-properties: fill;
fill: var(--comment-text-color)
}
.autofill-item-box > .profile-label-col > .profile-label {
font-size: calc(var(--label-font-size) / var(--default-font-size) * 1em);
unicode-bidi: plaintext;
}
.autofill-item-box > .profile-comment-col {
margin-inline-start: var(--col-spacer);
text-align: end;
color: var(--comment-text-color);
}
.autofill-item-box > .profile-comment-col > .profile-comment {
font-size: calc(var(--comment-font-size) / var(--default-font-size) * 1em);
unicode-bidi: plaintext;
}
.autofill-item-box[size="small"] {
flex-direction: column;
}
.autofill-item-box[size="small"] > .profile-comment-col {
margin-top: var(--row-spacer);
text-align: start;
}

View File

@ -1,20 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
@namespace url("http://www.w3.org/1999/xhtml");
@namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
.autofill-item-box {
--default-font-size: 12;
}
@media (prefers-contrast) {
xul|richlistitem[originaltype="autofill-profile"][selected="true"] > .autofill-item-box {
background-color: SelectedItem;
}
.autofill-item-box {
--comment-text-color: GrayText;
}
}

View File

@ -7,22 +7,6 @@ add_task(async function setup_storage() {
await setStorage(TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3); await setStorage(TEST_ADDRESS_1, TEST_ADDRESS_2, TEST_ADDRESS_3);
}); });
async function reopenPopupWithResizedInput(browser, selector, newSize) {
await closePopup(browser);
/* eslint no-shadow: ["error", { "allow": ["selector", "newSize"] }] */
await SpecialPowers.spawn(
browser,
[{ selector, newSize }],
async function ({ selector, newSize }) {
const input = content.document.querySelector(selector);
input.style.boxSizing = "border-box";
input.style.width = newSize + "px";
}
);
await openPopupOn(browser, selector);
}
add_task(async function test_address_dropdown() { add_task(async function test_address_dropdown() {
await BrowserTestUtils.withNewTab( await BrowserTestUtils.withNewTab(
{ gBrowser, url: URL }, { gBrowser, url: URL },
@ -33,20 +17,6 @@ add_task(async function test_address_dropdown() {
is(firstItem.getAttribute("ac-image"), "", "Should not show icon"); is(firstItem.getAttribute("ac-image"), "", "Should not show icon");
// The breakpoint of two-lines layout is 150px
await reopenPopupWithResizedInput(browser, focusInput, 140);
is(
firstItem._itemBox.getAttribute("size"),
"small",
"Show two-lines layout"
);
await reopenPopupWithResizedInput(browser, focusInput, 160);
is(
firstItem._itemBox.hasAttribute("size"),
false,
"Show one-line layout"
);
await closePopup(browser); await closePopup(browser);
} }
); );

View File

@ -79,7 +79,7 @@ add_task(async function test_credit_card_dropdown_icon_invalid_types_select() {
const creditCardItems = getDisplayedPopupItems( const creditCardItems = getDisplayedPopupItems(
browser, browser,
"[originaltype='autofill-profile']" "[originaltype='autofill']"
); );
for (const [index, creditCardItem] of creditCardItems.entries()) { for (const [index, creditCardItem] of creditCardItems.entries()) {

View File

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

View File

@ -59,6 +59,11 @@ async function setupFormHistory() {
]); ]);
} }
function replaceStars(str)
{
return str.replaceAll("*", "•")
}
initPopupListener(); initPopupListener();
// Form with history only. // Form with history only.
@ -86,7 +91,7 @@ add_task(async function all_saved_fields_less_than_threshold() {
synthesizeKey("KEY_ArrowDown"); synthesizeKey("KEY_ArrowDown");
checkMenuEntries([reducedMockRecord].map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries([reducedMockRecord].map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc["cc-name"], primary: cc["cc-name"],
secondary: cc.ccNumberFmt, secondary: replaceStars(cc.ccNumberFmt),
ariaLabel: `Visa ${cc["cc-name"]} ${cc.ccNumberFmt}`, ariaLabel: `Visa ${cc["cc-name"]} ${cc.ccNumberFmt}`,
image: expected.image, image: expected.image,
}))); })));
@ -102,8 +107,8 @@ add_task(async function check_menu_when_both_existed() {
await expectPopup(); await expectPopup();
synthesizeKey("KEY_ArrowDown"); synthesizeKey("KEY_ArrowDown");
checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc.ccNumberFmt, primary: replaceStars(cc.ccNumberFmt),
secondary: cc["cc-name"], secondary: cc["cc-name"].toString(),
ariaLabel: `${getCCTypeName(cc)} ${cc.ccNumberFmt.replaceAll("*", "")} ${cc["cc-name"]}`, ariaLabel: `${getCCTypeName(cc)} ${cc.ccNumberFmt.replaceAll("*", "")} ${cc["cc-name"]}`,
image: expected.image, image: expected.image,
}))); })));
@ -112,8 +117,8 @@ add_task(async function check_menu_when_both_existed() {
await expectPopup(); await expectPopup();
synthesizeKey("KEY_ArrowDown"); synthesizeKey("KEY_ArrowDown");
checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc["cc-name"], primary: cc["cc-name"].toString(),
secondary: cc.ccNumberFmt, secondary: replaceStars(cc.ccNumberFmt),
ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt}`, ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt}`,
image: expected.image, image: expected.image,
}))); })));
@ -122,8 +127,8 @@ add_task(async function check_menu_when_both_existed() {
await expectPopup(); await expectPopup();
synthesizeKey("KEY_ArrowDown"); synthesizeKey("KEY_ArrowDown");
checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc["cc-exp-year"], primary: cc["cc-exp-year"].toString(),
secondary: cc.ccNumberFmt, secondary: replaceStars(cc.ccNumberFmt),
ariaLabel: `${getCCTypeName(cc)} ${cc["cc-exp-year"]} ${cc.ccNumberFmt}`, ariaLabel: `${getCCTypeName(cc)} ${cc["cc-exp-year"]} ${cc.ccNumberFmt}`,
image: expected.image, image: expected.image,
}))); })));
@ -132,8 +137,8 @@ add_task(async function check_menu_when_both_existed() {
await expectPopup(); await expectPopup();
synthesizeKey("KEY_ArrowDown"); synthesizeKey("KEY_ArrowDown");
checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc["cc-exp-month"], primary: cc["cc-exp-month"].toString(),
secondary: cc.ccNumberFmt, secondary: replaceStars(cc.ccNumberFmt),
ariaLabel: `${getCCTypeName(cc)} ${cc["cc-exp-month"]} ${cc.ccNumberFmt}`, ariaLabel: `${getCCTypeName(cc)} ${cc["cc-exp-month"]} ${cc.ccNumberFmt}`,
image: expected.image, image: expected.image,
}))); })));
@ -185,8 +190,8 @@ add_task(async function check_fields_after_form_autofill() {
// The popup doesn't auto-show on focus because the field isn't empty // The popup doesn't auto-show on focus because the field isn't empty
await expectPopup(); await expectPopup();
checkMenuEntries(MOCK_STORAGE.slice(1).map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries(MOCK_STORAGE.slice(1).map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc["cc-exp-year"], primary: cc["cc-exp-year"].toString(),
secondary: cc.ccNumberFmt, secondary: replaceStars(cc.ccNumberFmt),
ariaLabel: `${getCCTypeName(cc)} ${cc["cc-exp-year"]} ${cc.ccNumberFmt}`, ariaLabel: `${getCCTypeName(cc)} ${cc["cc-exp-year"]} ${cc.ccNumberFmt}`,
image: expected.image, image: expected.image,
}))); })));
@ -220,7 +225,7 @@ add_task(async function check_cc_popup_on_field_blank() {
await expectPopup(); await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc["cc-name"], primary: cc["cc-name"],
secondary: cc.ccNumberFmt, secondary: replaceStars(cc.ccNumberFmt),
ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt}`, ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt}`,
image: expected.image, image: expected.image,
}))); })));
@ -240,7 +245,7 @@ add_task(async function check_form_autofill_resume() {
await expectPopup(); await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc["cc-name"], primary: cc["cc-name"],
secondary: cc.ccNumberFmt, secondary: replaceStars(cc.ccNumberFmt),
ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt}`, ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt}`,
image: expected.image, image: expected.image,
}))); })));

View File

@ -49,6 +49,11 @@ async function setupFormHistory() {
]); ]);
} }
function replaceStars(str)
{
return str.replaceAll("*", "•")
}
initPopupListener(); initPopupListener();
// Show Form History popup for non-autocomplete="off" field only // Show Form History popup for non-autocomplete="off" field only
@ -73,7 +78,7 @@ add_task(async function check_menu_when_both_with_autocomplete_off() {
synthesizeKey("KEY_ArrowDown"); synthesizeKey("KEY_ArrowDown");
await expectPopup(); await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc.ccNumberFmt, primary: replaceStars(cc.ccNumberFmt),
secondary: cc["cc-name"], secondary: cc["cc-name"],
ariaLabel: `${getCCTypeName(cc)} ${cc.ccNumberFmt.replaceAll("*", "")} ${cc["cc-name"]}`, ariaLabel: `${getCCTypeName(cc)} ${cc.ccNumberFmt.replaceAll("*", "")} ${cc["cc-name"]}`,
image: expected.image, image: expected.image,
@ -84,7 +89,7 @@ add_task(async function check_menu_when_both_with_autocomplete_off() {
await expectPopup(); await expectPopup();
checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({ checkMenuEntries(MOCK_STORAGE.map(patchRecordCCNumber).map(({ cc, expected }) => JSON.stringify({
primary: cc["cc-name"], primary: cc["cc-name"],
secondary: cc.ccNumberFmt, secondary: replaceStars(cc.ccNumberFmt),
ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt}`, ariaLabel: `${getCCTypeName(cc)} ${cc["cc-name"]} ${cc.ccNumberFmt}`,
image: expected.image, image: expected.image,
}))); })));

View File

@ -65,7 +65,7 @@ let addressTestCases = [
items: [ items: [
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[0]), comment: JSON.stringify(matchingProfiles[0]),
label: JSON.stringify({ label: JSON.stringify({
primary: "Sesame Street", primary: "Sesame Street",
@ -76,7 +76,7 @@ let addressTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[1]), comment: JSON.stringify(matchingProfiles[1]),
label: JSON.stringify({ label: JSON.stringify({
primary: "Mozilla", primary: "Mozilla",
@ -101,7 +101,7 @@ let addressTestCases = [
items: [ items: [
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[0]), comment: JSON.stringify(matchingProfiles[0]),
label: JSON.stringify({ label: JSON.stringify({
primary: "1-345-345-3456.", primary: "1-345-345-3456.",
@ -112,7 +112,7 @@ let addressTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[1]), comment: JSON.stringify(matchingProfiles[1]),
label: JSON.stringify({ label: JSON.stringify({
primary: "1-650-903-0800", primary: "1-650-903-0800",
@ -123,7 +123,7 @@ let addressTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[2]), comment: JSON.stringify(matchingProfiles[2]),
label: JSON.stringify({ label: JSON.stringify({
primary: "1-000-000-0000", primary: "1-000-000-0000",
@ -148,7 +148,7 @@ let addressTestCases = [
items: [ items: [
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[0]), comment: JSON.stringify(matchingProfiles[0]),
label: JSON.stringify({ label: JSON.stringify({
primary: "123 Sesame Street.", primary: "123 Sesame Street.",
@ -159,7 +159,7 @@ let addressTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[1]), comment: JSON.stringify(matchingProfiles[1]),
label: JSON.stringify({ label: JSON.stringify({
primary: "331 E. Evelyn Avenue", primary: "331 E. Evelyn Avenue",
@ -170,7 +170,7 @@ let addressTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[2]), comment: JSON.stringify(matchingProfiles[2]),
label: JSON.stringify({ label: JSON.stringify({
primary: "321, No Name St. 2nd line 3rd line", primary: "321, No Name St. 2nd line 3rd line",
@ -195,7 +195,7 @@ let addressTestCases = [
items: [ items: [
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[0]), comment: JSON.stringify(matchingProfiles[0]),
label: JSON.stringify({ label: JSON.stringify({
primary: "123 Sesame Street.", primary: "123 Sesame Street.",
@ -206,7 +206,7 @@ let addressTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[1]), comment: JSON.stringify(matchingProfiles[1]),
label: JSON.stringify({ label: JSON.stringify({
primary: "331 E. Evelyn Avenue", primary: "331 E. Evelyn Avenue",
@ -217,7 +217,7 @@ let addressTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[2]), comment: JSON.stringify(matchingProfiles[2]),
label: JSON.stringify({ label: JSON.stringify({
primary: "321, No Name St.", primary: "321, No Name St.",
@ -298,11 +298,11 @@ let creditCardTestCases = [
items: [ items: [
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[0]), comment: JSON.stringify(matchingProfiles[0]),
label: JSON.stringify({ label: JSON.stringify({
primary: "Timothy Berners-Lee", primary: "Timothy Berners-Lee",
secondary: "****6785", secondary: "••••6785",
ariaLabel: "Visa Timothy Berners-Lee ****6785", ariaLabel: "Visa Timothy Berners-Lee ****6785",
image: "chrome://formautofill/content/third-party/cc-logo-visa.svg", image: "chrome://formautofill/content/third-party/cc-logo-visa.svg",
}), }),
@ -310,11 +310,11 @@ let creditCardTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[1]), comment: JSON.stringify(matchingProfiles[1]),
label: JSON.stringify({ label: JSON.stringify({
primary: "John Doe", primary: "John Doe",
secondary: "****1234", secondary: "••••1234",
ariaLabel: "American Express John Doe ****1234", ariaLabel: "American Express John Doe ****1234",
image: "chrome://formautofill/content/third-party/cc-logo-amex.png", image: "chrome://formautofill/content/third-party/cc-logo-amex.png",
}), }),
@ -336,10 +336,10 @@ let creditCardTestCases = [
items: [ items: [
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[0]), comment: JSON.stringify(matchingProfiles[0]),
label: JSON.stringify({ label: JSON.stringify({
primary: "****6785", primary: "••••6785",
secondary: "Timothy Berners-Lee", secondary: "Timothy Berners-Lee",
ariaLabel: "Visa 6785 Timothy Berners-Lee", ariaLabel: "Visa 6785 Timothy Berners-Lee",
image: "chrome://formautofill/content/third-party/cc-logo-visa.svg", image: "chrome://formautofill/content/third-party/cc-logo-visa.svg",
@ -348,10 +348,10 @@ let creditCardTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[1]), comment: JSON.stringify(matchingProfiles[1]),
label: JSON.stringify({ label: JSON.stringify({
primary: "****1234", primary: "••••1234",
secondary: "John Doe", secondary: "John Doe",
ariaLabel: "American Express 1234 John Doe", ariaLabel: "American Express 1234 John Doe",
image: "chrome://formautofill/content/third-party/cc-logo-amex.png", image: "chrome://formautofill/content/third-party/cc-logo-amex.png",
@ -360,10 +360,10 @@ let creditCardTestCases = [
}, },
{ {
value: "", value: "",
style: "autofill-profile", style: "autofill",
comment: JSON.stringify(matchingProfiles[2]), comment: JSON.stringify(matchingProfiles[2]),
label: JSON.stringify({ label: JSON.stringify({
primary: "****5678", primary: "••••5678",
secondary: "", secondary: "",
ariaLabel: "5678", ariaLabel: "5678",
image: "chrome://formautofill/content/icon-credit-card-generic.svg", image: "chrome://formautofill/content/icon-credit-card-generic.svg",

View File

@ -178,6 +178,13 @@
opacity: 1; opacity: 1;
} }
&[originaltype="autofill"] > .two-line-wrapper > .ac-site-icon {
width: auto;
min-height: 16px;
max-width: none; /* reset max-width so that credit card icons don't appear stretched */
max-height: 16px;
}
/* Insecure field warning */ /* Insecure field warning */
&[originaltype="insecureWarning"] { &[originaltype="insecureWarning"] {
background-color: var(--arrowpanel-dimmed); background-color: var(--arrowpanel-dimmed);

View File

@ -609,7 +609,7 @@ export const GeckoViewAutocomplete = {
); );
break; break;
} }
case "autofill-profile": { case "autofill": {
const comment = JSON.parse(option.comment); const comment = JSON.parse(option.comment);
debug`delegateSelection ${comment}`; debug`delegateSelection ${comment}`;
const creditCard = CreditCard.fromGecko(comment); const creditCard = CreditCard.fromGecko(comment);

View File

@ -247,7 +247,7 @@ export class AutoCompleteParent extends JSWindowActorParent {
// the scrollbar in login or form autofill popups. // the scrollbar in login or form autofill popups.
if ( if (
resultStyles.size && resultStyles.size &&
(resultStyles.has("autofill-profile") || resultStyles.has("loginsFooter")) (resultStyles.has("autofill") || resultStyles.has("loginsFooter"))
) { ) {
this.openedPopup._normalMaxRows = this.openedPopup.maxRows; this.openedPopup._normalMaxRows = this.openedPopup.maxRows;
this.openedPopup.mInput.maxRows = 10; this.openedPopup.mInput.maxRows = 10;

View File

@ -422,8 +422,7 @@ export const ProfileAutocomplete = {
if ( if (
selectedIndex == -1 || selectedIndex == -1 ||
!this.lastProfileAutoCompleteResult || !this.lastProfileAutoCompleteResult ||
this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != "autofill"
"autofill-profile"
) { ) {
await this.sendFillRequestToFormAutofillParent(focusedInput, comment); await this.sendFillRequestToFormAutofillParent(focusedInput, comment);
return; return;
@ -456,8 +455,7 @@ export const ProfileAutocomplete = {
if ( if (
!this.lastProfileAutoCompleteResult || !this.lastProfileAutoCompleteResult ||
this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != this.lastProfileAutoCompleteResult.getStyleAt(selectedIndex) != "autofill"
"autofill-profile"
) { ) {
return; return;
} }

View File

@ -478,11 +478,9 @@ export class FormAutofillChild extends JSWindowActorChild {
return; return;
} }
const doc = this.document;
switch (message.name) { switch (message.name) {
case "FormAutofill:PreviewProfile": { case "FormAutofill:PreviewProfile": {
this.previewProfile(doc); this.previewProfile(message.data.selectedIndex);
break; break;
} }
case "FormAutofill:ClearForm": { case "FormAutofill:ClearForm": {
@ -658,9 +656,7 @@ export class FormAutofillChild extends JSWindowActorChild {
} }
} }
previewProfile(doc) { previewProfile(selectedIndex) {
let docWin = doc.ownerGlobal;
let selectedIndex = lazy.ProfileAutocomplete._getSelectedIndex(docWin);
let lastAutoCompleteResult = let lastAutoCompleteResult =
lazy.ProfileAutocomplete.lastProfileAutoCompleteResult; lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
let focusedInput = this.activeInput; let focusedInput = this.activeInput;
@ -669,7 +665,7 @@ export class FormAutofillChild extends JSWindowActorChild {
selectedIndex === -1 || selectedIndex === -1 ||
!focusedInput || !focusedInput ||
!lastAutoCompleteResult || !lastAutoCompleteResult ||
lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile" lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill"
) { ) {
lazy.ProfileAutocomplete._clearProfilePreview(); lazy.ProfileAutocomplete._clearProfilePreview();
} else { } else {

View File

@ -84,21 +84,6 @@ export let FormAutofillStatus = {
Services.prefs.addObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this); Services.prefs.addObserver(ENABLED_AUTOFILL_CREDITCARDS_PREF, this);
} }
// We have to use empty window type to get all opened windows here because the
// window type parameter may not be available during startup.
for (let win of Services.wm.getEnumerator("")) {
let { documentElement } = win.document;
if (documentElement?.getAttribute("windowtype") == "navigator:browser") {
this.injectElements(win.document);
} else {
// Manually call onOpenWindow for windows that are already opened but not
// yet have the window type set. This ensures we inject the elements we need
// when its docuemnt is ready.
this.onOpenWindow(win);
}
}
Services.wm.addListener(this);
Services.telemetry.setEventRecordingEnabled("creditcard", true); Services.telemetry.setEventRecordingEnabled("creditcard", true);
Services.telemetry.setEventRecordingEnabled("address", true); Services.telemetry.setEventRecordingEnabled("address", true);
}, },
@ -198,31 +183,6 @@ export let FormAutofillStatus = {
this.updateStatus(); this.updateStatus();
}, },
injectElements(doc) {
Services.scriptloader.loadSubScript(
"chrome://formautofill/content/customElements.js",
doc.ownerGlobal
);
},
onOpenWindow(xulWindow) {
const win = xulWindow.docShell.domWindow;
win.addEventListener(
"load",
() => {
if (
win.document.documentElement.getAttribute("windowtype") ==
"navigator:browser"
) {
this.injectElements(win.document);
}
},
{ once: true }
);
},
onCloseWindow() {},
async observe(subject, topic, data) { async observe(subject, topic, data) {
lazy.log.debug("observe:", topic, "with data:", data); lazy.log.debug("observe:", topic, "with data:", data);
switch (topic) { switch (topic) {

View File

@ -181,7 +181,7 @@ class ProfileAutoCompleteResult {
case "insecure": case "insecure":
return "insecureWarning"; return "insecureWarning";
default: default:
return "autofill-profile"; return "autofill";
} }
} }
@ -541,8 +541,8 @@ export class CreditCardResult extends ProfileAutoCompleteResult {
.filter(chunk => !!chunk) // Exclude empty chunks. .filter(chunk => !!chunk) // Exclude empty chunks.
.join(" "); .join(" ");
return { return {
primary, primary: primary.toString().replaceAll("*", "•"),
secondary, secondary: secondary.toString().replaceAll("*", "•"),
ariaLabel, ariaLabel,
image, image,
}; };

View File

@ -411,7 +411,7 @@
// The styles on the list which have different <content> structure and overrided // The styles on the list which have different <content> structure and overrided
// _adjustAcItem() are unreusable. // _adjustAcItem() are unreusable.
const UNREUSEABLE_STYLES = [ const UNREUSEABLE_STYLES = [
"autofill-profile", "autofill",
"action", "action",
"status", "status",
"generatedPassword", "generatedPassword",
@ -436,8 +436,8 @@
if (!reusable) { if (!reusable) {
let options = null; let options = null;
switch (style) { switch (style) {
case "autofill-profile": case "autofill":
options = { is: "autocomplete-profile-listitem" }; options = { is: "autocomplete-autofill-richlistitem" };
break; break;
case "action": case "action":
options = { is: "autocomplete-action-richlistitem" }; options = { is: "autocomplete-action-richlistitem" };

View File

@ -725,6 +725,11 @@
// and, optionally a secondary label, for example: // and, optionally a secondary label, for example:
// { "fillMessageName": "Fill:Clear", secondary: "Second Label" } // { "fillMessageName": "Fill:Clear", secondary: "Second Label" }
class MozAutocompleteActionRichlistitem extends MozAutocompleteTwoLineRichlistitem { class MozAutocompleteActionRichlistitem extends MozAutocompleteTwoLineRichlistitem {
constructor() {
super();
this.selectedByMouseOver = true;
}
_adjustAcItem() { _adjustAcItem() {
super._adjustAcItem(); super._adjustAcItem();
@ -782,6 +787,58 @@
} }
} }
class MozAutocompleteAutoFillRichlistitem extends MozAutocompleteTwoLineRichlistitem {
constructor() {
super();
this.selectedByMouseOver = true;
}
_adjustAcItem() {
let { primary, secondary, ariaLabel } = JSON.parse(
this.getAttribute("ac-value")
);
let line1Label = this.querySelector(".line1-label");
line1Label.textContent = primary.toString();
let line2Label = this.querySelector(".line2-label");
line2Label.textContent = secondary.toString();
if (ariaLabel) {
this.setAttribute("aria-label", ariaLabel);
}
this.querySelector(".ac-site-icon").collapsed =
this.getAttribute("ac-image") == "";
}
set selected(val) {
if (val) {
this.setAttribute("selected", "true");
} else {
this.removeAttribute("selected");
}
let { AutoCompleteParent } = ChromeUtils.importESModule(
"resource://gre/actors/AutoCompleteParent.sys.mjs"
);
let actor = AutoCompleteParent.getCurrentActor();
if (!actor) {
return;
}
let selectedIndex = val ? this.control.getIndexOfItem(this) : -1;
actor.manager
.getActor("FormAutofill")
.sendAsyncMessage("FormAutofill:PreviewProfile", { selectedIndex });
}
get selected() {
return this.getAttribute("selected") == "true";
}
}
class MozAutocompleteGeneratedPasswordRichlistitem extends MozAutocompleteTwoLineRichlistitem { class MozAutocompleteGeneratedPasswordRichlistitem extends MozAutocompleteTwoLineRichlistitem {
constructor() { constructor() {
super(); super();
@ -906,6 +963,14 @@
} }
); );
customElements.define(
"autocomplete-autofill-richlistitem",
MozAutocompleteAutoFillRichlistitem,
{
extends: "richlistitem",
}
);
customElements.define( customElements.define(
"autocomplete-login-richlistitem", "autocomplete-login-richlistitem",
MozAutocompleteLoginRichlistitem, MozAutocompleteLoginRichlistitem,