Repository URL to install this package:
|
Version:
69.0-1 ▾
|
PK
!<µ)«. . chrome.manifestlocale formautofill en-US en-US/locale/en-US/
PK
!<H>÷ߤ ¤ api.js/* 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/. */
"use strict";
/* globals ExtensionAPI */
const CACHED_STYLESHEETS = new WeakMap();
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
const { XPCOMUtils } = ChromeUtils.import(
"resource://gre/modules/XPCOMUtils.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"FormAutofill",
"resource://formautofill/FormAutofill.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"formAutofillParent",
"resource://formautofill/FormAutofillParent.jsm"
);
XPCOMUtils.defineLazyServiceGetter(
this,
"resProto",
"@mozilla.org/network/protocol;1?name=resource",
"nsISubstitutingProtocolHandler"
);
const RESOURCE_HOST = "formautofill";
function insertStyleSheet(domWindow, url) {
let doc = domWindow.document;
let styleSheetAttr = `href="${url}" type="text/css"`;
let styleSheet = doc.createProcessingInstruction(
"xml-stylesheet",
styleSheetAttr
);
doc.insertBefore(styleSheet, doc.documentElement);
if (CACHED_STYLESHEETS.has(domWindow)) {
CACHED_STYLESHEETS.get(domWindow).push(styleSheet);
} else {
CACHED_STYLESHEETS.set(domWindow, [styleSheet]);
}
}
function onMaybeOpenPopup(evt) {
let domWindow = evt.target.ownerGlobal;
if (CACHED_STYLESHEETS.has(domWindow)) {
// This window already has autofill stylesheets.
return;
}
insertStyleSheet(domWindow, "chrome://formautofill/content/formautofill.css");
insertStyleSheet(
domWindow,
"resource://formautofill/autocomplete-item-shared.css"
);
insertStyleSheet(domWindow, "resource://formautofill/autocomplete-item.css");
}
function isAvailable() {
let availablePref = Services.prefs.getCharPref(
"extensions.formautofill.available"
);
if (availablePref == "on") {
return true;
} else if (availablePref == "detect") {
let locale = Services.locale.requestedLocale;
let region = Services.prefs.getCharPref("browser.search.region", "");
let supportedCountries = Services.prefs
.getCharPref("extensions.formautofill.supportedCountries")
.split(",");
if (
!Services.prefs.getBoolPref("extensions.formautofill.supportRTL") &&
Services.locale.isAppLocaleRTL
) {
return false;
}
return locale == "en-US" && supportedCountries.includes(region);
}
return false;
}
this.formautofill = class extends ExtensionAPI {
onStartup() {
// We have to do this before actually determining if we're enabled, since
// there are scripts inside of the core browser code that depend on the
// FormAutofill JSMs being registered.
let uri = Services.io.newURI("chrome/res/", null, this.extension.rootURI);
resProto.setSubstitution(RESOURCE_HOST, uri);
let aomStartup = Cc[
"@mozilla.org/addons/addon-manager-startup;1"
].getService(Ci.amIAddonManagerStartup);
const manifestURI = Services.io.newURI(
"manifest.json",
null,
this.extension.rootURI
);
this.chromeHandle = aomStartup.registerChrome(manifestURI, [
["content", "formautofill", "chrome/content/"],
]);
// Until we move to fluent (bug 1446164), we're stuck with
// chrome.manifest for handling localization since its what the
// build system can handle for localized repacks.
if (this.extension.rootURI instanceof Ci.nsIJARURI) {
this.autofillManifest = this.extension.rootURI.JARFile.QueryInterface(
Ci.nsIFileURL
).file;
} else if (this.extension.rootURI instanceof Ci.nsIFileURL) {
this.autofillManifest = this.extension.rootURI.file;
}
if (this.autofillManifest) {
Components.manager.addBootstrappedManifestLocation(this.autofillManifest);
} else {
Cu.reportError(
"Cannot find formautofill chrome.manifest for registring translated strings"
);
}
if (!isAvailable()) {
Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill");
// reset the sync related prefs incase the feature was previously available
// but isn't now.
Services.prefs.clearUserPref("services.sync.engine.addresses.available");
Services.prefs.clearUserPref(
"services.sync.engine.creditcards.available"
);
Services.telemetry.scalarSet("formautofill.availability", false);
return;
}
// This pref is used for web contents to detect the autocomplete feature.
// When it's true, "element.autocomplete" will return tokens we currently
// support -- otherwise it'll return an empty string.
Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true);
Services.telemetry.scalarSet("formautofill.availability", true);
// This pref determines whether the "addresses"/"creditcards" sync engine is
// available (ie, whether it is shown in any UI etc) - it *does not* determine
// whether the engine is actually enabled or not.
Services.prefs.setBoolPref(
"services.sync.engine.addresses.available",
true
);
if (FormAutofill.isAutofillCreditCardsAvailable) {
Services.prefs.setBoolPref(
"services.sync.engine.creditcards.available",
true
);
} else {
Services.prefs.clearUserPref(
"services.sync.engine.creditcards.available"
);
}
// Listen for the autocomplete popup message to lazily append our stylesheet related to the popup.
Services.mm.addMessageListener(
"FormAutoComplete:MaybeOpenPopup",
onMaybeOpenPopup
);
formAutofillParent.init().catch(Cu.reportError);
Services.mm.loadFrameScript(
"chrome://formautofill/content/FormAutofillFrameScript.js",
true,
true
);
}
onShutdown(isAppShutdown) {
if (isAppShutdown) {
return;
}
resProto.setSubstitution(RESOURCE_HOST, null);
this.chromeHandle.destruct();
this.chromeHandle = null;
if (this.autofillManifest) {
Components.manager.removeBootstrappedManifestLocation(
this.autofillManifest
);
}
Services.mm.removeMessageListener(
"FormAutoComplete:MaybeOpenPopup",
onMaybeOpenPopup
);
for (let win of Services.wm.getEnumerator("navigator:browser")) {
let cachedStyleSheets = CACHED_STYLESHEETS.get(win);
if (!cachedStyleSheets) {
continue;
}
while (cachedStyleSheets.length !== 0) {
cachedStyleSheets.pop().remove();
}
}
}
};
PK
!<¿C
background.js/* eslint-env webextensions */
"use strict";
browser.runtime.onUpdateAvailable.addListener(details => {
// By listening to but ignoring this event, any updates will
// be delayed until the next browser restart.
// Note that if we ever wanted to change this, we should make
// sure we manually invalidate the startup cache using the
// startupcache-invalidate notification.
});
PK
!<Ëû¯* ) chrome/content/FormAutofillFrameScript.js/* 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/. */
/**
* Form Autofill frame script.
*/
"use strict";
/* eslint-env mozilla/frame-script */
var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
ChromeUtils.defineModuleGetter(
this,
"setTimeout",
"resource://gre/modules/Timer.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"FormAutofill",
"resource://formautofill/FormAutofill.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"FormAutofillContent",
"resource://formautofill/FormAutofillContent.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"FormAutofillUtils",
"resource://formautofill/FormAutofillUtils.jsm"
);
/**
* Handles content's interactions for the frame.
*/
var FormAutofillFrameScript = {
_nextHandleElement: null,
_alreadyDOMContentLoaded: false,
_hasDOMContentLoadedHandler: false,
_hasPendingTask: false,
_doIdentifyAutofillFields() {
if (this._hasPendingTask) {
return;
}
this._hasPendingTask = true;
setTimeout(() => {
FormAutofillContent.identifyAutofillFields(this._nextHandleElement);
this._hasPendingTask = false;
this._nextHandleElement = null;
// This is for testing purpose only which sends a message to indicate that the
// form has been identified, and ready to open popup.
sendAsyncMessage("FormAutofill:FieldsIdentified");
FormAutofillContent.updateActiveInput();
});
},
init() {
addEventListener("focusin", this);
addEventListener("DOMFormBeforeSubmit", this);
addMessageListener("FormAutofill:PreviewProfile", this);
addMessageListener("FormAutofill:ClearForm", this);
addMessageListener("FormAutoComplete:PopupClosed", this);
addMessageListener("FormAutoComplete:PopupOpened", this);
},
handleEvent(evt) {
if (!evt.isTrusted || !FormAutofill.isAutofillEnabled) {
return;
}
switch (evt.type) {
case "focusin": {
this.onFocusIn(evt);
break;
}
case "DOMFormBeforeSubmit": {
this.onDOMFormBeforeSubmit(evt);
break;
}
default: {
throw new Error("Unexpected event type");
}
}
},
onFocusIn(evt) {
FormAutofillContent.updateActiveInput();
let element = evt.target;
if (!FormAutofillUtils.isFieldEligibleForAutofill(element)) {
return;
}
this._nextHandleElement = element;
if (!this._alreadyDOMContentLoaded) {
let doc = element.ownerDocument;
if (doc.readyState === "loading") {
if (!this._hasDOMContentLoadedHandler) {
this._hasDOMContentLoadedHandler = true;
doc.addEventListener(
"DOMContentLoaded",
() => this._doIdentifyAutofillFields(),
{ once: true }
);
}
return;
}
this._alreadyDOMContentLoaded = true;
}
this._doIdentifyAutofillFields();
},
/**
* Handle the DOMFormBeforeSubmit event.
* @param {Event} evt
*/
onDOMFormBeforeSubmit(evt) {
let formElement = evt.target;
if (!FormAutofill.isAutofillEnabled) {
return;
}
FormAutofillContent.formSubmitted(formElement);
},
receiveMessage(message) {
if (!FormAutofill.isAutofillEnabled) {
return;
}
const doc = content.document;
const { chromeEventHandler } = doc.ownerGlobal.docShell;
switch (message.name) {
case "FormAutofill:PreviewProfile": {
FormAutofillContent.previewProfile(doc);
break;
}
case "FormAutofill:ClearForm": {
FormAutofillContent.clearForm();
break;
}
case "FormAutoComplete:PopupClosed": {
FormAutofillContent.onPopupClosed(message.data.selectedRowStyle);
Services.tm.dispatchToMainThread(() => {
chromeEventHandler.removeEventListener(
"keydown",
FormAutofillContent._onKeyDown,
true
);
});
break;
}
case "FormAutoComplete:PopupOpened": {
chromeEventHandler.addEventListener(
"keydown",
FormAutofillContent._onKeyDown,
true
);
break;
}
}
},
};
FormAutofillFrameScript.init();
PK
!<üBõN õN # chrome/content/autofillEditForms.js/* 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/. */
/* exported EditAddress, EditCreditCard */
/* eslint-disable mozilla/balanced-listeners */ // Not relevant since the document gets unloaded.
"use strict";
class EditAutofillForm {
constructor(elements) {
this._elements = elements;
}
/**
* Fill the form with a record object.
* @param {object} [record = {}]
*/
loadRecord(record = {}) {
for (let field of this._elements.form.elements) {
let value = record[field.id];
value = typeof value == "undefined" ? "" : value;
if (record.guid) {
field.value = value;
} else if (field.localName == "select") {
this.setDefaultSelectedOptionByValue(field, value);
} else {
// Use .defaultValue instead of .value to avoid setting the `dirty` flag
// which triggers form validation UI.
field.defaultValue = value;
}
}
if (!record.guid) {
// Reset the dirty value flag and validity state.
this._elements.form.reset();
} else {
for (let field of this._elements.form.elements) {
this.updatePopulatedState(field);
this.updateCustomValidity(field);
}
}
}
setDefaultSelectedOptionByValue(select, value) {
for (let option of select.options) {
option.defaultSelected = option.value == value;
}
}
/**
* Get a record from the form suitable for a save/update in storage.
* @returns {object}
*/
buildFormObject() {
let initialObject = {};
if (this.hasMailingAddressFields) {
// Start with an empty string for each mailing-address field so that any
// fields hidden for the current country are blanked in the return value.
initialObject = {
"street-address": "",
"address-level3": "",
"address-level2": "",
"address-level1": "",
"postal-code": "",
};
}
return Array.from(this._elements.form.elements).reduce((obj, input) => {
if (!input.disabled) {
obj[input.id] = input.value;
}
return obj;
}, initialObject);
}
/**
* Handle events
*
* @param {DOMEvent} event
*/
handleEvent(event) {
switch (event.type) {
case "change": {
this.handleChange(event);
break;
}
case "input": {
this.handleInput(event);
break;
}
}
}
/**
* Handle change events
*
* @param {DOMEvent} event
*/
handleChange(event) {
this.updatePopulatedState(event.target);
}
/**
* Handle input events
*
* @param {DOMEvent} event
*/
handleInput(event) {}
/**
* Attach event listener
*/
attachEventListeners() {
this._elements.form.addEventListener("input", this);
}
/**
* Set the field-populated attribute if the field has a value.
*
* @param {DOMElement} field The field that will be checked for a value.
*/
updatePopulatedState(field) {
let span = field.parentNode.querySelector(".label-text");
if (!span) {
return;
}
span.toggleAttribute("field-populated", !!field.value.trim());
}
/**
* Run custom validity routines specific to the field and type of form.
*
* @param {DOMElement} field The field that will be validated.
*/
updateCustomValidity(field) {}
}
class EditAddress extends EditAutofillForm {
/**
* @param {HTMLElement[]} elements
* @param {object} record
* @param {object} config
* @param {string[]} config.DEFAULT_REGION
* @param {function} config.getFormFormat Function to return form layout info for a given country.
* @param {function} config.findAddressSelectOption Finds the matching select option for a given
select element, address, and fieldName.
* @param {string[]} config.countries
* @param {boolean} [config.noValidate=undefined] Whether to validate the form
*/
constructor(elements, record, config) {
super(elements);
Object.assign(this, config);
let { form } = this._elements;
Object.assign(this._elements, {
addressLevel3Label: form.querySelector(
"#address-level3-container > .label-text"
),
addressLevel2Label: form.querySelector(
"#address-level2-container > .label-text"
),
addressLevel1Label: form.querySelector(
"#address-level1-container > .label-text"
),
postalCodeLabel: form.querySelector(
"#postal-code-container > .label-text"
),
country: form.querySelector("#country"),
});
this.populateCountries();
// Need to populate the countries before trying to set the initial country.
// Also need to use this._record so it has the default country selected.
this.loadRecord(record);
this.attachEventListeners();
form.noValidate = !!config.noValidate;
}
loadRecord(record) {
this._record = record;
if (!record) {
record = {
country: this.DEFAULT_REGION,
};
}
let { addressLevel1Options } = this.getFormFormat(record.country);
this.populateAddressLevel1(addressLevel1Options, record.country);
super.loadRecord(record);
this.loadAddressLevel1(record["address-level1"], record.country);
this.formatForm(record.country);
}
get hasMailingAddressFields() {
let { addressFields } = this._elements.form.dataset;
return (
!addressFields ||
addressFields
.trim()
.split(/\s+/)
.includes("mailing-address")
);
}
/**
* `mailing-address` is a special attribute token to indicate mailing fields + country.
*
* @param {object[]} mailingFieldsOrder - `fieldsOrder` from `getFormFormat`
* @param {string} addressFields - white-space-separated string of requested address fields to show
* @returns {object[]} in the same structure as `mailingFieldsOrder` but including non-mail fields
*/
static computeVisibleFields(mailingFieldsOrder, addressFields) {
if (addressFields) {
let requestedFieldClasses = addressFields.trim().split(/\s+/);
let fieldClasses = [];
if (requestedFieldClasses.includes("mailing-address")) {
fieldClasses = fieldClasses.concat(mailingFieldsOrder);
// `country` isn't part of the `mailingFieldsOrder` so add it when filling a mailing-address
requestedFieldClasses.splice(
requestedFieldClasses.indexOf("mailing-address"),
1,
"country"
);
}
for (let fieldClassName of requestedFieldClasses) {
fieldClasses.push({
fieldId: fieldClassName,
newLine: fieldClassName == "name",
});
}
return fieldClasses;
}
// This is the default which is shown in the management interface and includes all fields.
return mailingFieldsOrder.concat([
{
fieldId: "country",
},
{
fieldId: "tel",
},
{
fieldId: "email",
newLine: true,
},
]);
}
/**
* Format the form based on country. The address-level1 and postal-code labels
* should be specific to the given country.
* @param {string} country
*/
formatForm(country) {
const {
addressLevel3Label,
addressLevel2Label,
addressLevel1Label,
addressLevel1Options,
postalCodeLabel,
fieldsOrder: mailingFieldsOrder,
postalCodePattern,
countryRequiredFields,
} = this.getFormFormat(country);
this._elements.addressLevel3Label.dataset.localization = addressLevel3Label;
this._elements.addressLevel2Label.dataset.localization = addressLevel2Label;
this._elements.addressLevel1Label.dataset.localization = addressLevel1Label;
this._elements.postalCodeLabel.dataset.localization = postalCodeLabel;
let addressFields = this._elements.form.dataset.addressFields;
let extraRequiredFields = this._elements.form.dataset.extraRequiredFields;
let fieldClasses = EditAddress.computeVisibleFields(
mailingFieldsOrder,
addressFields
);
let requiredFields = new Set(countryRequiredFields);
if (extraRequiredFields) {
for (let extraRequiredField of extraRequiredFields.trim().split(/\s+/)) {
requiredFields.add(extraRequiredField);
}
}
this.arrangeFields(fieldClasses, requiredFields);
this.updatePostalCodeValidation(postalCodePattern);
this.populateAddressLevel1(addressLevel1Options, country);
}
/**
* Update address field visibility and order based on libaddressinput data.
*
* @param {object[]} fieldsOrder array of objects with `fieldId` and optional `newLine` properties
* @param {Set} requiredFields Set of `fieldId` strings that mark which fields are required
*/
arrangeFields(fieldsOrder, requiredFields) {
/**
* @see FormAutofillStorage.VALID_ADDRESS_FIELDS
*/
let fields = [
// `name` is a wrapper for the 3 name fields.
"name",
"organization",
"street-address",
"address-level3",
"address-level2",
"address-level1",
"postal-code",
"country",
"tel",
"email",
];
let inputs = [];
for (let i = 0; i < fieldsOrder.length; i++) {
let { fieldId, newLine } = fieldsOrder[i];
let container = this._elements.form.querySelector(
`#${fieldId}-container`
);
let containerInputs = [
...container.querySelectorAll("input, textarea, select"),
];
containerInputs.forEach(function(input) {
input.disabled = false;
// libaddressinput doesn't list 'country' or 'name' as required.
// The additional-name field should never get marked as required.
input.required =
(fieldId == "country" ||
fieldId == "name" ||
requiredFields.has(fieldId)) &&
input.id != "additional-name";
});
inputs.push(...containerInputs);
container.style.display = "flex";
container.style.order = i;
container.style.pageBreakAfter = newLine ? "always" : "auto";
// Remove the field from the list of fields
fields.splice(fields.indexOf(fieldId), 1);
}
for (let i = 0; i < inputs.length; i++) {
// Assign tabIndex starting from 1
inputs[i].tabIndex = i + 1;
}
// Hide the remaining fields
for (let field of fields) {
let container = this._elements.form.querySelector(`#${field}-container`);
container.style.display = "none";
for (let input of [
...container.querySelectorAll("input, textarea, select"),
]) {
input.disabled = true;
}
}
}
updatePostalCodeValidation(postalCodePattern) {
let postalCodeInput = this._elements.form.querySelector("#postal-code");
if (postalCodePattern && postalCodeInput.style.display != "none") {
postalCodeInput.setAttribute("pattern", postalCodePattern);
} else {
postalCodeInput.removeAttribute("pattern");
}
}
/**
* Set the address-level1 value on the form field (input or select, whichever is present).
*
* @param {string} addressLevel1Value Value of the address-level1 from the autofill record
* @param {string} country The corresponding country
*/
loadAddressLevel1(addressLevel1Value, country) {
let field = this._elements.form.querySelector("#address-level1");
if (field.localName == "input") {
field.value = addressLevel1Value || "";
return;
}
let matchedSelectOption = this.findAddressSelectOption(
field,
{
country,
"address-level1": addressLevel1Value,
},
"address-level1"
);
if (matchedSelectOption && !matchedSelectOption.selected) {
field.value = matchedSelectOption.value;
field.dispatchEvent(new Event("input", { bubbles: true }));
field.dispatchEvent(new Event("change", { bubbles: true }));
} else if (addressLevel1Value) {
// If the option wasn't found, insert an option at the beginning of
// the select that matches the stored value.
field.insertBefore(
new Option(addressLevel1Value, addressLevel1Value, true, true),
field.firstChild
);
}
}
/**
* Replace the text input for address-level1 with a select dropdown if
* a fixed set of names exists. Otherwise show a text input.
*
* @param {Map?} options Map of options with regionCode -> name mappings
* @param {string} country The corresponding country
*/
populateAddressLevel1(options, country) {
let field = this._elements.form.querySelector("#address-level1");
if (field.dataset.country == country) {
return;
}
if (!options) {
if (field.localName == "input") {
return;
}
let input = document.createElement("input");
input.setAttribute("type", "text");
input.id = "address-level1";
input.required = field.required;
input.disabled = field.disabled;
input.tabIndex = field.tabIndex;
field.replaceWith(input);
return;
}
if (field.localName == "input") {
let select = document.createElement("select");
select.id = "address-level1";
select.required = field.required;
select.disabled = field.disabled;
select.tabIndex = field.tabIndex;
field.replaceWith(select);
field = select;
}
field.textContent = "";
field.dataset.country = country;
let fragment = document.createDocumentFragment();
fragment.appendChild(new Option(undefined, undefined, true, true));
for (let [regionCode, regionName] of options) {
let option = new Option(regionName, regionCode);
fragment.appendChild(option);
}
field.appendChild(fragment);
}
populateCountries() {
let fragment = document.createDocumentFragment();
// Sort countries by their visible names.
let countries = [...this.countries.entries()].sort((e1, e2) =>
e1[1].localeCompare(e2[1])
);
for (let country of countries) {
let option = new Option();
option.value = country[0];
option.dataset.localizationRegion = country[0].toLowerCase();
fragment.appendChild(option);
}
this._elements.country.appendChild(fragment);
}
handleChange(event) {
if (event.target == this._elements.country) {
this.formatForm(event.target.value);
}
super.handleChange(event);
}
attachEventListeners() {
this._elements.form.addEventListener("change", this);
super.attachEventListeners();
}
}
class EditCreditCard extends EditAutofillForm {
/**
* @param {HTMLElement[]} elements
* @param {object} record with a decrypted cc-number
* @param {object} addresses in an object with guid keys for the billing address picker.
* @param {object} config
* @param {function} config.isCCNumber Function to determine if a string is a valid CC number.
* @param {function} config.getSupportedNetworks Function to get the list of card networks
*/
constructor(elements, record, addresses, config) {
super(elements);
this._addresses = addresses;
Object.assign(this, config);
Object.assign(this._elements, {
ccNumber: this._elements.form.querySelector("#cc-number"),
invalidCardNumberStringElement: this._elements.form.querySelector(
"#invalidCardNumberString"
),
month: this._elements.form.querySelector("#cc-exp-month"),
year: this._elements.form.querySelector("#cc-exp-year"),
ccType: this._elements.form.querySelector("#cc-type"),
billingAddress: this._elements.form.querySelector("#billingAddressGUID"),
billingAddressRow: this._elements.form.querySelector(
".billingAddressRow"
),
});
this.attachEventListeners();
this.loadRecord(record, addresses);
}
loadRecord(record, addresses, preserveFieldValues) {
// _record must be updated before generateYears and generateBillingAddressOptions are called.
this._record = record;
this._addresses = addresses;
this.generateBillingAddressOptions(preserveFieldValues);
if (!preserveFieldValues) {
// Re-populating the networks will reset the selected option.
this.populateNetworks();
// Re-generating the months will reset the selected option.
this.generateMonths();
// Re-generating the years will reset the selected option.
this.generateYears();
super.loadRecord(record);
}
}
generateMonths() {
const count = 12;
// Clear the list
this._elements.month.textContent = "";
// Empty month option
this._elements.month.appendChild(new Option());
// Populate month list. Format: "month number - month name"
let dateFormat = new Intl.DateTimeFormat(navigator.language, {
month: "long",
}).format;
for (let i = 0; i < count; i++) {
let monthNumber = (i + 1).toString();
let monthName = dateFormat(new Date(1970, i));
let option = new Option();
option.value = monthNumber;
// XXX: Bug 1446164 - Localize this string.
option.textContent = `${monthNumber.padStart(2, "0")} - ${monthName}`;
this._elements.month.appendChild(option);
}
}
generateYears() {
const count = 11;
const currentYear = new Date().getFullYear();
const ccExpYear = this._record && this._record["cc-exp-year"];
// Clear the list
this._elements.year.textContent = "";
// Provide an empty year option
this._elements.year.appendChild(new Option());
if (ccExpYear && ccExpYear < currentYear) {
this._elements.year.appendChild(new Option(ccExpYear));
}
for (let i = 0; i < count; i++) {
let year = currentYear + i;
let option = new Option(year);
this._elements.year.appendChild(option);
}
if (ccExpYear && ccExpYear > currentYear + count) {
this._elements.year.appendChild(new Option(ccExpYear));
}
}
populateNetworks() {
// Clear the list
this._elements.ccType.textContent = "";
let frag = document.createDocumentFragment();
// include an empty first option
frag.appendChild(new Option("", ""));
let supportedNetworks = this.getSupportedNetworks();
for (let id of supportedNetworks) {
let option = new Option();
option.value = id;
option.dataset.localization = "cardNetwork." + id;
frag.appendChild(option);
}
this._elements.ccType.appendChild(frag);
}
generateBillingAddressOptions(preserveFieldValues) {
let billingAddressGUID;
if (preserveFieldValues && this._elements.billingAddress.value) {
billingAddressGUID = this._elements.billingAddress.value;
} else if (this._record) {
billingAddressGUID = this._record.billingAddressGUID;
}
this._elements.billingAddress.textContent = "";
this._elements.billingAddress.appendChild(new Option("", ""));
let hasAddresses = false;
for (let [guid, address] of Object.entries(this._addresses)) {
hasAddresses = true;
let selected = guid == billingAddressGUID;
let option = new Option(
this.getAddressLabel(address),
guid,
selected,
selected
);
this._elements.billingAddress.appendChild(option);
}
this._elements.billingAddressRow.hidden = !hasAddresses;
}
attachEventListeners() {
this._elements.form.addEventListener("change", this);
super.attachEventListeners();
}
handleInput(event) {
// Clear the error message if cc-number is valid
if (
event.target == this._elements.ccNumber &&
this.isCCNumber(this._elements.ccNumber.value)
) {
this._elements.ccNumber.setCustomValidity("");
}
super.handleInput(event);
}
updateCustomValidity(field) {
super.updateCustomValidity(field);
// Mark the cc-number field as invalid if the number is empty or invalid.
if (field == this._elements.ccNumber && !this.isCCNumber(field.value)) {
let invalidCardNumberString = this._elements
.invalidCardNumberStringElement.textContent;
field.setCustomValidity(invalidCardNumberString || " ");
}
}
}
PK
!<Xª*¼0 ¼0 chrome/content/customElements.js/* 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.
(() => {
const { Services } = ChromeUtils.import(
"resource://gre/modules/Services.jsm"
);
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 {
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this.textContent = "";
this.appendChild(
MozXULElement.parseXULToFragment(`
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box">
<div class="profile-label-col profile-item-col">
<span class="profile-label-affix"></span>
<span class="profile-label"></span>
</div>
<div class="profile-comment-col profile-item-col">
<span class="profile-comment"></span>
</div>
</div>
`)
);
this._itemBox = this.querySelector(".autofill-item-box");
this._labelAffix = this.querySelector(".profile-label-affix");
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");
}
let { AutoCompletePopup } = ChromeUtils.import(
"resource://gre/modules/AutoCompletePopup.jsm"
);
AutoCompletePopup.sendMessageToBrowser("FormAutofill:PreviewProfile");
return val;
}
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 { primaryAffix, primary, secondary } = JSON.parse(
this.getAttribute("ac-value")
);
this._labelAffix.textContent = primaryAffix;
this._label.textContent = primary;
this._comment.textContent = secondary;
}
};
customElements.define(
"autocomplete-profile-listitem",
MozElements.MozAutocompleteProfileListitem,
{ extends: "richlistitem" }
);
class MozAutocompleteProfileListitemFooter extends MozAutocompleteProfileListitemBase {
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(
MozXULElement.parseXULToFragment(`
<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>
`)
);
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 {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() {
/* global messageManager */
if (this.showWarningText) {
messageManager.removeMessageListener(
"FormAutofill:UpdateWarningMessage",
this._updateWarningNote
);
}
this._itemBox.removeAttribute("no-warning");
}
_adjustAcItem() {
/* global Cu */
this._adjustAutofillItemLayout();
this.setAttribute("formautofillattached", "true");
let { AppConstants } = ChromeUtils.import(
"resource://gre/modules/AppConstants.jsm",
{}
);
// TODO: The "Short" suffix is pointless now as normal version string is no longer needed,
// we should consider removing the suffix if possible when the next time locale change.
let buttonTextBundleKey =
AppConstants.platform == "macosx"
? "autocompleteFooterOptionOSXShort"
: "autocompleteFooterOptionShort";
let buttonText = this._stringBundle.GetStringFromName(
buttonTextBundleKey
);
this._optionButton.textContent = buttonText;
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) {
messageManager.addMessageListener(
"FormAutofill:UpdateWarningMessage",
this._updateWarningNote
);
this._updateWarningNote();
} else {
this._itemBox.setAttribute("no-warning", "true");
}
}
}
customElements.define(
"autocomplete-profile-listitem-footer",
MozAutocompleteProfileListitemFooter,
{ extends: "richlistitem" }
);
class MozAutocompleteCreditcardInsecureField extends MozAutocompleteProfileListitemBase {
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this.textContent = "";
this.appendChild(
MozXULElement.parseXULToFragment(`
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-insecure-item"></div>
`)
);
this._itemBox = this.querySelector(".autofill-insecure-item");
this._adjustAcItem();
}
set selected(val) {
// Make this item unselectable since we see this item as a pure message.
return false;
}
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 {
constructor() {
super();
this.addEventListener("click", event => {
if (event.button != 0) {
return;
}
let { AutoCompletePopup } = ChromeUtils.import(
"resource://gre/modules/AutoCompletePopup.jsm"
);
AutoCompletePopup.sendMessageToBrowser("FormAutofill:ClearForm");
});
}
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this.textContent = "";
this.appendChild(
MozXULElement.parseXULToFragment(`
<div xmlns="http://www.w3.org/1999/xhtml" class="autofill-item-box autofill-footer">
<div class="autofill-footer-row autofill-button"></div>
</div>
`)
);
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" }
);
})();
PK
!<r<ÓÆ Æ chrome/content/editAddress.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title data-localization="addNewAddressTitle"/>
<link rel="stylesheet" href="resource://formautofill/editDialog-shared.css"/>
<link rel="stylesheet" href="resource://formautofill/editAddress.css"/>
<link rel="stylesheet" href="resource://formautofill/editDialog.css"/>
<script src="chrome://formautofill/content/l10n.js"></script>
<script src="chrome://formautofill/content/editDialog.js"></script>
<script src="chrome://formautofill/content/autofillEditForms.js"></script>
</head>
<body dir="&locale.dir;">
<form id="form" class="editAddressForm" autocomplete="off">
<!--
The <span class="label-text" …/> needs to be after the form field in the same element in
order to get proper label styling with :focus and :moz-ui-invalid.
-->
<div id="name-container" class="container">
<label id="given-name-container">
<input id="given-name" type="text" required="required"/>
<span data-localization="givenName" class="label-text"/>
</label>
<label id="additional-name-container">
<input id="additional-name" type="text"/>
<span data-localization="additionalName" class="label-text"/>
</label>
<label id="family-name-container">
<input id="family-name" type="text" required="required"/>
<span data-localization="familyName" class="label-text"/>
</label>
</div>
<label id="organization-container" class="container">
<input id="organization" type="text"/>
<span data-localization="organization2" class="label-text"/>
</label>
<label id="street-address-container" class="container">
<textarea id="street-address" rows="3"/>
<span data-localization="streetAddress" class="label-text"/>
</label>
<label id="address-level3-container" class="container">
<input id="address-level3" type="text"/>
<span class="label-text"/>
</label>
<label id="address-level2-container" class="container">
<input id="address-level2" type="text"/>
<span class="label-text"/>
</label>
<label id="address-level1-container" class="container">
<!-- The address-level1 input will get replaced by a select dropdown
by autofillEditForms.js when the selected country has provided
specific options. -->
<input id="address-level1" type="text"/>
<span class="label-text"/>
</label>
<label id="postal-code-container" class="container">
<input id="postal-code" type="text"/>
<span class="label-text"/>
</label>
<label id="country-container" class="container">
<select id="country" required="required">
<option/>
</select>
<span data-localization="country" class="label-text"/>
</label>
<label id="tel-container" class="container">
<input id="tel" type="tel"/>
<span data-localization="tel" class="label-text"/>
</label>
<label id="email-container" class="container">
<input id="email" type="email" required="required"/>
<span data-localization="email" class="label-text"/>
</label>
</form>
<div id="controls-container">
<button id="cancel" data-localization="cancelBtnLabel"/>
<button id="save" data-localization="saveBtnLabel"/>
<span id="country-warning-message" data-localization="countryWarningMessage2"/>
</div>
<script><![CDATA[
"use strict";
/* import-globals-from l10n.js */
let {
DEFAULT_REGION,
countries,
} = FormAutofill;
let {
getFormFormat,
findAddressSelectOption,
} = FormAutofillUtils;
let args = window.arguments || [];
let {
record,
noValidate,
} = args[0] || {};
/* import-globals-from autofillEditForms.js */
var fieldContainer = new EditAddress({
form: document.getElementById("form"),
}, record, {
DEFAULT_REGION,
getFormFormat: getFormFormat.bind(FormAutofillUtils),
findAddressSelectOption: findAddressSelectOption.bind(FormAutofillUtils),
countries,
noValidate,
});
/* import-globals-from editDialog.js */
new EditAddressDialog({
title: document.querySelector("title"),
fieldContainer,
controlsContainer: document.getElementById("controls-container"),
cancel: document.getElementById("cancel"),
save: document.getElementById("save"),
}, record);
]]></script>
</body>
</html>
PK
!<- É* # chrome/content/editCreditCard.xhtml<?xml version="1.0" encoding="UTF-8"?>
<!-- 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/. -->
<!DOCTYPE html [
<!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
%globalDTD;
]>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title data-localization="addNewCreditCardTitle"/>
<link rel="stylesheet" href="resource://formautofill/editDialog-shared.css"/>
<link rel="stylesheet" href="resource://formautofill/editCreditCard.css"/>
<link rel="stylesheet" href="resource://formautofill/editDialog.css"/>
<script src="chrome://formautofill/content/l10n.js"></script>
<script src="chrome://formautofill/content/editDialog.js"></script>
<script src="chrome://formautofill/content/autofillEditForms.js"></script>
</head>
<body dir="&locale.dir;">
<form id="form" class="editCreditCardForm" autocomplete="off">
<!--
The <span class="label-text" …/> needs to be after the form field in the same element in
order to get proper label styling with :focus and :moz-ui-invalid.
-->
<label id="cc-number-container" class="container">
<span id="invalidCardNumberString" hidden="hidden" data-localization="invalidCardNumber"></span>
<input id="cc-number" type="text" required="required" minlength="9" pattern="[- 0-9]+"/>
<span data-localization="cardNumber" class="label-text"/>
</label>
<label id="cc-exp-month-container" class="container">
<select id="cc-exp-month" required="required">
<option/>
</select>
<span data-localization="cardExpiresMonth" class="label-text"/>
</label>
<label id="cc-exp-year-container" class="container">
<select id="cc-exp-year" required="required">
<option/>
</select>
<span data-localization="cardExpiresYear" class="label-text"/>
</label>
<label id="cc-name-container" class="container">
<input id="cc-name" type="text" required="required"/>
<span data-localization="nameOnCard" class="label-text"/>
</label>
<label id="cc-type-container" class="container">
<select id="cc-type" required="required">
</select>
<span data-localization="cardNetwork" class="label-text"/>
</label>
<label id="cc-csc-container" class="container" hidden="hidden">
<!-- The CSC container will get filled in by forms that need a CSC (using csc-input.js) -->
</label>
<div id="billingAddressGUID-container" class="billingAddressRow container rich-picker">
<select id="billingAddressGUID" required="required">
</select>
<label for="billingAddressGUID" data-localization="billingAddress" class="label-text"/>
</div>
</form>
<div id="controls-container">
<button id="cancel" data-localization="cancelBtnLabel"/>
<button id="save" data-localization="saveBtnLabel"/>
</div>
<script><![CDATA[
"use strict";
/* import-globals-from l10n.js */
(async () => {
let {
getAddressLabel,
isCCNumber,
getCreditCardNetworks,
} = FormAutofillUtils;
let args = window.arguments || [];
let {
record,
} = args[0] || {};
let addresses = {};
for (let address of await formAutofillStorage.addresses.getAll()) {
addresses[address.guid] = address;
}
/* import-globals-from autofillEditForms.js */
let fieldContainer = new EditCreditCard({
form: document.getElementById("form"),
}, record, addresses,
{
getAddressLabel: getAddressLabel.bind(FormAutofillUtils),
isCCNumber: isCCNumber.bind(FormAutofillUtils),
getSupportedNetworks: getCreditCardNetworks.bind(FormAutofillUtils),
});
/* import-globals-from editDialog.js */
new EditCreditCardDialog({
title: document.querySelector("title"),
fieldContainer,
controlsContainer: document.getElementById("controls-container"),
cancel: document.getElementById("cancel"),
save: document.getElementById("save"),
}, record);
})();
]]></script>
</body>
</html>
PK
!<no§@ @ chrome/content/editDialog.js/* 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/. */
/* exported EditAddressDialog, EditCreditCardDialog */
/* eslint-disable mozilla/balanced-listeners */ // Not relevant since the document gets unloaded.
"use strict";
// eslint-disable-next-line no-unused-vars
const { FormAutofill } = ChromeUtils.import(
"resource://formautofill/FormAutofill.jsm"
);
ChromeUtils.defineModuleGetter(
this,
"formAutofillStorage",
"resource://formautofill/FormAutofillStorage.jsm"
);
class AutofillEditDialog {
constructor(subStorageName, elements, record) {
this._storageInitPromise = formAutofillStorage.initialize();
this._subStorageName = subStorageName;
this._elements = elements;
this._record = record;
this.localizeDocument();
window.addEventListener("DOMContentLoaded", this, { once: true });
}
async init() {
this.updateSaveButtonState();
this.attachEventListeners();
// For testing only: signal to tests that the dialog is ready for testing.
// This is likely no longer needed since retrieving from storage is fully
// handled in manageDialog.js now.
window.dispatchEvent(new CustomEvent("FormReady"));
}
/**
* Get storage and ensure it has been initialized.
* @returns {object}
*/
async getStorage() {
await this._storageInitPromise;
return formAutofillStorage[this._subStorageName];
}
/**
* Asks FormAutofillParent to save or update an record.
* @param {object} record
* @param {string} guid [optional]
*/
async saveRecord(record, guid) {
let storage = await this.getStorage();
if (guid) {
await storage.update(guid, record);
} else {
await storage.add(record);
}
}
/**
* Handle events
*
* @param {DOMEvent} event
*/
handleEvent(event) {
switch (event.type) {
case "DOMContentLoaded": {
this.init();
break;
}
case "click": {
this.handleClick(event);
break;
}
case "input": {
this.handleInput(event);
break;
}
case "keypress": {
this.handleKeyPress(event);
break;
}
case "contextmenu": {
if (
!(event.target instanceof HTMLInputElement) &&
!(event.target instanceof HTMLTextAreaElement)
) {
event.preventDefault();
}
break;
}
}
}
/**
* Handle click events
*
* @param {DOMEvent} event
*/
handleClick(event) {
if (event.target == this._elements.cancel) {
window.close();
}
if (event.target == this._elements.save) {
this.handleSubmit();
}
}
/**
* Handle input events
*
* @param {DOMEvent} event
*/
handleInput(event) {
this.updateSaveButtonState();
}
/**
* Handle key press events
*
* @param {DOMEvent} event
*/
handleKeyPress(event) {
if (event.keyCode == KeyEvent.DOM_VK_ESCAPE) {
window.close();
}
}
updateSaveButtonState() {
// Toggle disabled attribute on the save button based on
// whether the form is filled or empty.
if (
Object.keys(this._elements.fieldContainer.buildFormObject()).length == 0
) {
this._elements.save.setAttribute("disabled", true);
} else {
this._elements.save.removeAttribute("disabled");
}
}
/**
* Attach event listener
*/
attachEventListeners() {
window.addEventListener("keypress", this);
window.addEventListener("contextmenu", this);
this._elements.controlsContainer.addEventListener("click", this);
document.addEventListener("input", this);
}
// An interface to be inherited.
localizeDocument() {}
}
class EditAddressDialog extends AutofillEditDialog {
constructor(elements, record) {
super("addresses", elements, record);
}
localizeDocument() {
if (this._record) {
this._elements.title.dataset.localization = "editAddressTitle";
}
}
async handleSubmit() {
await this.saveRecord(
this._elements.fieldContainer.buildFormObject(),
this._record ? this._record.guid : null
);
window.close();
}
}
class EditCreditCardDialog extends AutofillEditDialog {
constructor(elements, record) {
super("creditCards", elements, record);
}
localizeDocument() {
if (this._record) {
this._elements.title.dataset.localization = "editCreditCardTitle";
}
}
async handleSubmit() {
let creditCard = this._elements.fieldContainer.buildFormObject();
if (!this._elements.fieldContainer._elements.form.reportValidity()) {
return;
}
try {
await this.saveRecord(
creditCard,
this._record ? this._record.guid : null
);
window.close();
} catch (ex) {
Cu.reportError(ex);
}
}
}
PK
!<*1- - chrome/content/formautofill.css/* 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/. */
#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-profile"],
#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"],
#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-insecureWarning"],
#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-clear-button"] {
display: block;
margin: 0;
padding: 0;
height: auto;
min-height: auto;
-moz-binding: none;
}
/* Treat @collpased="true" as display: none similar to how it is for XUL elements.
* https://developer.mozilla.org/en-US/docs/Web/CSS/visibility#Values */
#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-profile"][collapsed="true"],
#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-footer"][collapsed="true"],
#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-insecureWarning"][collapsed="true"],
#PopupAutoComplete > richlistbox > richlistitem[originaltype="autofill-clear-button"][collapsed="true"] {
display: none;
}
#PopupAutoComplete[resultstyles~="autofill-profile"] {
min-width: 150px !important;
}
#PopupAutoComplete[resultstyles~="autofill-insecureWarning"] {
min-width: 200px !important;
}
/* Form Autofill Doorhanger */
#autofill-address-notification popupnotificationcontent > .desc-message-box,
#autofill-credit-card-notification popupnotificationcontent > .desc-message-box {
margin-block-end: 12px;
}
#autofill-credit-card-notification popupnotificationcontent > .desc-message-box > image {
margin-inline-start: 6px;
width: 16px;
height: 16px;
list-style-image: url(chrome://formautofill/content/icon-credit-card-generic.svg);
}
#autofill-address-notification popupnotificationcontent > .desc-message-box > description,
#autofill-credit-card-notification popupnotificationcontent > .desc-message-box > description {
font-style: italic;
}
PK
!<¡iet " chrome/content/formfill-anchor.svg<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16" fill="context-fill" fill-opacity="context-fill-opacity">
<path d="M7.3 6h1.5c.1 0 .2-.1.2-.3V2c0-.5-.4-1-1-1s-1 .4-1 1v3.8c0 .1.1.2.3.2z"/>
<path d="M13.5 3H11c-.6 0-1 .4-1 1s.4 1 1 1h2.5c.3 0 .5.2.5.5v7c0 .3-.2.5-.5.5h-11c-.3 0-.5-.3-.5-.5v-7c0-.3.2-.5.5-.5H5c.6 0 1-.4 1-1s-.4-1-1-1H2.5C1.1 3 0 4.1 0 5.5v7C0 13.8 1.1 15 2.5 15h11c1.4 0 2.5-1.1 2.5-2.5v-7C16 4.1 14.9 3 13.5 3z"/>
<path d="M3.6 7h2.8c.3 0 .6.2.6.5v2.8c0 .4-.3.7-.6.7H3.6c-.3 0-.6-.3-.6-.6V7.5c0-.3.3-.5.6-.5zM9.5 8h3c.3 0 .5-.3.5-.5s-.2-.5-.5-.5h-3c-.3 0-.5.2-.5.5s.2.5.5.5zM9.5 9c-.3 0-.5.2-.5.5s.2.5.5.5h2c.3 0 .5-.2.5-.5s-.2-.5-.5-.5h-2z"/>
</svg>
PK
!<@_{µ! µ! " chrome/content/heuristicsRegexp.js/* 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/. */
/*
* Form Autofill field Heuristics RegExp.
*/
/* exported HeuristicsRegExp */
"use strict";
var HeuristicsRegExp = {
// These regular expressions are from Chromium source codes [1]. Most of them
// converted to JS format have the same meaning with the original ones except
// the first line of "address-level1".
// [1] https://cs.chromium.org/chromium/src/components/autofill/core/browser/autofill_regex_constants.cc
RULES: {
// ==== Email ====
email: new RegExp(
"e.?mail" +
"|courriel" + // fr
"|メールアドレス" + // ja-JP
"|Электронной.?Почты" + // ru
"|邮件|邮箱" + // zh-CN
"|電郵地址" + // zh-TW
"|(?:이메일|전자.?우편|[Ee]-?mail)(.?주소)?", // ko-KR
"iu"
),
// ==== Telephone ====
tel: new RegExp(
"phone|mobile|contact.?number" +
"|telefonnummer" + // de-DE
"|telefono|teléfono" + // es
"|telfixe" + // fr-FR
"|電話" + // ja-JP
"|telefone|telemovel" + // pt-BR, pt-PT
"|телефон" + // ru
"|电话" + // zh-CN
"|(?:전화|핸드폰|휴대폰|휴대전화)(?:.?번호)?", // ko-KR
"iu"
),
// ==== Address Fields ====
organization: new RegExp(
"company|business|organization|organisation" +
"|firma|firmenname" + // de-DE
"|empresa" + // es
"|societe|société" + // fr-FR
"|ragione.?sociale" + // it-IT
"|会社" + // ja-JP
"|название.?компании" + // ru
"|单位|公司" + // zh-CN
"|회사|직장", // ko-KR
"iu"
),
"street-address": new RegExp("streetaddress|street-address", "iu"),
"address-line1": new RegExp(
"^address$|address[_-]?line(one)?|address1|addr1|street" +
"|addrline1|address_1" + // Extra rules by Firefox
"|(?:shipping|billing)address$" +
"|strasse|straße|hausnummer|housenumber" + // de-DE
"|house.?name" + // en-GB
"|direccion|dirección" + // es
"|adresse" + // fr-FR
"|indirizzo" + // it-IT
"|^住所$|住所1" + // ja-JP
"|morada|endereço" + // pt-BR, pt-PT
"|Адрес" + // ru
"|地址" + // zh-CN
"|^주소.?$|주소.?1", // ko-KR
"iu"
),
"address-line2": new RegExp(
"address[_-]?line(2|two)|address2|addr2|street|suite|unit" +
"|addrline2|address_2" + // Extra rules by Firefox
"|adresszusatz|ergänzende.?angaben" + // de-DE
"|direccion2|colonia|adicional" + // es
"|addresssuppl|complementnom|appartement" + // fr-FR
"|indirizzo2" + // it-IT
"|住所2" + // ja-JP
"|complemento|addrcomplement" + // pt-BR, pt-PT
"|Улица" + // ru
"|地址2" + // zh-CN
"|주소.?2", // ko-KR
"iu"
),
"address-line3": new RegExp(
"address[_-]?line(3|three)|address3|addr3|street|suite|unit" +
"|addrline3|address_3" + // Extra rules by Firefox
"|adresszusatz|ergänzende.?angaben" + // de-DE
"|direccion3|colonia|adicional" + // es
"|addresssuppl|complementnom|appartement" + // fr-FR
"|indirizzo3" + // it-IT
"|住所3" + // ja-JP
"|complemento|addrcomplement" + // pt-BR, pt-PT
"|Улица" + // ru
"|地址3" + // zh-CN
"|주소.?3", // ko-KR
"iu"
),
"address-level2": new RegExp(
"city|town" +
"|\\bort\\b|stadt" + // de-DE
"|suburb" + // en-AU
"|ciudad|provincia|localidad|poblacion" + // es
"|ville|commune" + // fr-FR
"|localita" + // it-IT
"|市区町村" + // ja-JP
"|cidade" + // pt-BR, pt-PT
"|Город" + // ru
"|市" + // zh-CN
"|分區" + // zh-TW
"|^시[^도·・]|시[·・]?군[·・]?구", // ko-KR
"iu"
),
"address-level1": new RegExp(
// JS does not support backward matching, so the following pattern is
// applied in FormAutofillHeuristics.getInfo() rather than regexp.
// "(?<!united )state|county|region|province"
"state|county|region|province" +
"|land" + // de-DE
"|county|principality" + // en-UK
"|都道府県" + // ja-JP
"|estado|provincia" + // pt-BR, pt-PT
"|область" + // ru
"|省" + // zh-CN
"|地區" + // zh-TW
"|^시[·・]?도", // ko-KR
"iu"
),
"postal-code": new RegExp(
"zip|postal|post.*code|pcode" +
"|pin.?code" + // en-IN
"|postleitzahl" + // de-DE
"|\\bcp\\b" + // es
"|\\bcdp\\b" + // fr-FR
"|\\bcap\\b" + // it-IT
"|郵便番号" + // ja-JP
"|codigo|codpos|\\bcep\\b" + // pt-BR, pt-PT
"|Почтовый.?Индекс" + // ru
"|邮政编码|邮编" + // zh-CN
"|郵遞區號" + // zh-TW
"|우편.?번호", // ko-KR
"iu"
),
country: new RegExp(
"country|countries" +
"|país|pais" + // es
"|国" + // ja-JP
"|国家" + // zh-CN
"|국가|나라", // ko-KR
"iu"
),
// ==== Name Fields ====
name: new RegExp(
"^name|full.?name|your.?name|customer.?name|bill.?name|ship.?name" +
"|name.*first.*last|firstandlastname" +
"|nombre.*y.*apellidos" + // es
"|^nom" + // fr-FR
"|お名前|氏名" + // ja-JP
"|^nome" + // pt-BR, pt-PT
"|姓名" + // zh-CN
"|성명", // ko-KR
"iu"
),
"given-name": new RegExp(
"first.*name|initials|fname|first$|given.*name" +
"|vorname" + // de-DE
"|nombre" + // es
"|forename|prénom|prenom" + // fr-FR
"|名" + // ja-JP
"|nome" + // pt-BR, pt-PT
"|Имя" + // ru
"|이름", // ko-KR
"iu"
),
"additional-name": new RegExp(
"middle.*name|mname|middle$" +
"|apellido.?materno|lastlastname" + // es
// This rule is for middle initial.
"middle.*initial|m\\.i\\.|mi$|\\bmi\\b",
"iu"
),
"family-name": new RegExp(
"last.*name|lname|surname|last$|secondname|family.*name" +
"|nachname" + // de-DE
"|apellido" + // es
"|famille|^nom" + // fr-FR
"|cognome" + // it-IT
"|姓" + // ja-JP
"|morada|apelidos|surename|sobrenome" + // pt-BR, pt-PT
"|Фамилия" + // ru
"|\\b성(?:[^명]|\\b)", // ko-KR
"iu"
),
// ==== Credit Card Fields ====
"cc-name": new RegExp(
"card.?(?:holder|owner)|name.*(\\b)?on(\\b)?.*card" +
"|(?:card|cc).?name|cc.?full.?name" +
"|karteninhaber" + // de-DE
"|nombre.*tarjeta" + // es
"|nom.*carte" + // fr-FR
"|nome.*cart" + // it-IT
"|名前" + // ja-JP
"|Имя.*карты" + // ru
"|信用卡开户名|开户名|持卡人姓名" + // zh-CN
"|持卡人姓名", // zh-TW
"iu"
),
"cc-number": new RegExp(
"(add)?(?:card|cc|acct).?(?:number|#|no|num|field)" +
"|(cc|kk)nr" + // Extra rules by Firefox for de-DE
"|nummer" + // de-DE
"|credito|numero|número" + // es
"|numéro" + // fr-FR
"|カード番号" + // ja-JP
"|Номер.*карты" + // ru
"|信用卡号|信用卡号码" + // zh-CN
"|信用卡卡號" + // zh-TW
"|카드", // ko-KR
"iu"
),
"cc-exp-month": new RegExp(
"expir|exp.*mo|exp.*date|ccmonth|cardmonth|addmonth" +
"|(cc|kk)month" + // Extra rules by Firefox for de-DE
"|gueltig|gültig|monat" + // de-DE
"|fecha" + // es
"|date.*exp" + // fr-FR
"|scadenza" + // it-IT
"|有効期限" + // ja-JP
"|validade" + // pt-BR, pt-PT
"|Срок действия карты" + // ru
"|月", // zh-CN,
"iu"
),
"cc-exp-year": new RegExp(
"exp|^/|(add)?year" +
"|(cc|kk)year" + // Extra rules by Firefox for de-DE
"|ablaufdatum|gueltig|gültig|jahr" + // de-DE
"|fecha" + // es
"|scadenza" + // it-IT
"|有効期限" + // ja-JP
"|validade" + // pt-BR, pt-PT
"|Срок действия карты" + // ru
"|年|有效期", // zh-CN
"iu"
),
"cc-exp": new RegExp(
"expir|exp.*date|^expfield$" +
"|gueltig|gültig" + // de-DE
"|fecha" + // es
"|date.*exp" + // fr-FR
"|scadenza" + // it-IT
"|有効期限" + // ja-JP
"|validade" + // pt-BR, pt-PT
"|Срок действия карты", // ru
"iu"
),
},
};
PK
!<?Ó<Ñm m $ chrome/content/icon-address-save.svg<!-- 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/. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<path fill="#999899" d="M22 13.7H9.4c-.6 0-1.2.5-1.2 1.2 0 .6.5 1.2 1.2 1.2H22c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zM6.1 26.6V5.5c0-.8.7-1.5 1.5-1.5h16c.9 0 1.5.6 1.5 1.5V16h2V3.8c0-1-.7-1.8-1.8-1.8H5.9c-1 0-1.8.8-1.8 1.8v24.5c0 1 .8 1.7 1.8 1.7h9.3v-2H7.6c-.8 0-1.5-.6-1.5-1.4zm21.1-1.9h-2.5V20c0-.4-.3-.8-.8-.8h-3.1c-.4 0-.8.3-.8.8v4.6h-2.5c-.6 0-.8.4-.3.8l4.3 4.2c.2.2.5.3.8.3s.6-.1.8-.3l4.3-4.2c.6-.4.4-.7-.2-.7zm-11.3-5.6H9.4c-.6 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2h6.5c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2zM22 7.8H9.4c-.6 0-1.2.5-1.2 1.2s.5 1.2 1.2 1.2H22c.6 0 1.2-.5 1.2-1.2s-.6-1.2-1.2-1.2z"/>
</svg>
PK
!<0³Zqj j &