forked from mirrors/gecko-dev
Depends on D191355 Differential Revision: https://phabricator.services.mozilla.com/D191360
401 lines
12 KiB
JavaScript
401 lines
12 KiB
JavaScript
/* 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" }
|
|
);
|
|
|
|
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");
|
|
}
|
|
|
|
// 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 `
|
|
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-insecure-item"></div>
|
|
`;
|
|
}
|
|
|
|
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 `
|
|
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box autofill-footer">
|
|
<div class="autofill-footer-row autofill-button"></div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
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" }
|
|
);
|
|
})();
|