/* 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 `
`;
}
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" }
);
class MozAutocompleteProfileListitemFooter extends MozAutocompleteProfileListitemBase {
static get markup() {
return `
`;
}
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");
}
// After focusing a field that was previously filled with cc information,
// the "ac-image" is falsely set for the listitem-footer. For now it helps us
// to distinguish between address and cc footer. In the future this false attribute
// setting should be fixed and the "ac-image" check replaced by a different method.
const buttonTextBundleKey = !this.getAttribute("ac-image")
? "autocompleteManageAddresses"
: "autocompleteManageCreditCards";
const buttonText =
this._stringBundle.GetStringFromName(buttonTextBundleKey);
this._optionButton.textContent = buttonText;
}
}
customElements.define(
"autocomplete-profile-listitem-footer",
MozAutocompleteProfileListitemFooter,
{ extends: "richlistitem" }
);
class MozAutocompleteCreditcardInsecureField extends MozAutocompleteProfileListitemBase {
static get markup() {
return `
`;
}
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this.textContent = "";
this.appendChild(this.constructor.fragment);
this._itemBox = this.querySelector(".autofill-insecure-item");
this._adjustAcItem();
}
set selected(val) {
// This item is unselectable since we see this item as a pure message.
}
get selected() {
return this.getAttribute("selected") == "true";
}
_adjustAcItem() {
this._adjustAutofillItemLayout();
this.setAttribute("formautofillattached", "true");
let value = this.getAttribute("ac-value");
this._itemBox.textContent = value;
}
}
customElements.define(
"autocomplete-creditcard-insecure-field",
MozAutocompleteCreditcardInsecureField,
{ extends: "richlistitem" }
);
class MozAutocompleteProfileListitemClearButton extends MozAutocompleteProfileListitemBase {
static get markup() {
return `
`;
}
constructor() {
super();
this.addEventListener("click", event => {
if (event.button != 0) {
return;
}
sendMessageToBrowser("FormAutofill:ClearForm");
});
}
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this.textContent = "";
this.appendChild(this.constructor.fragment);
this._itemBox = this.querySelector(".autofill-item-box");
this._clearBtn = this.querySelector(".autofill-button");
this._adjustAcItem();
}
_adjustAcItem() {
this._adjustAutofillItemLayout();
this.setAttribute("formautofillattached", "true");
let clearFormBtnLabel =
this._stringBundle.GetStringFromName("clearFormBtnLabel2");
this._clearBtn.textContent = clearFormBtnLabel;
}
}
customElements.define(
"autocomplete-profile-listitem-clear-button",
MozAutocompleteProfileListitemClearButton,
{ extends: "richlistitem" }
);
})();