summaryrefslogtreecommitdiffstats
path: root/toolkit/components/formautofill
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/formautofill')
-rw-r--r--toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs2
-rw-r--r--toolkit/components/formautofill/Constants.ios.mjs3
-rw-r--r--toolkit/components/formautofill/FormAutofill.ios.sys.mjs2
-rw-r--r--toolkit/components/formautofill/FormAutofill.sys.mjs4
-rw-r--r--toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs14
-rw-r--r--toolkit/components/formautofill/FormAutofillChild.sys.mjs524
-rw-r--r--toolkit/components/formautofill/FormAutofillContent.sys.mjs367
-rw-r--r--toolkit/components/formautofill/FormAutofillNative.cpp20
-rw-r--r--toolkit/components/formautofill/FormAutofillParent.sys.mjs6
-rw-r--r--toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs193
-rw-r--r--toolkit/components/formautofill/FormAutofillSync.sys.mjs2
-rw-r--r--toolkit/components/formautofill/Helpers.ios.mjs100
-rw-r--r--toolkit/components/formautofill/Overrides.ios.js1
-rw-r--r--toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs62
-rw-r--r--toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs8
-rw-r--r--toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs10
-rw-r--r--toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs6
-rw-r--r--toolkit/components/formautofill/moz.build5
-rw-r--r--toolkit/components/formautofill/shared/AddressComponent.sys.mjs10
-rw-r--r--toolkit/components/formautofill/shared/AddressParser.sys.mjs2
-rw-r--r--toolkit/components/formautofill/shared/AddressRecord.sys.mjs119
-rw-r--r--toolkit/components/formautofill/shared/AutofillTelemetry.sys.mjs (renamed from toolkit/components/formautofill/AutofillTelemetry.sys.mjs)8
-rw-r--r--toolkit/components/formautofill/shared/FieldScanner.sys.mjs13
-rw-r--r--toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs72
-rw-r--r--toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs24
-rw-r--r--toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs39
-rw-r--r--toolkit/components/formautofill/shared/FormStateManager.sys.mjs2
-rw-r--r--toolkit/components/formautofill/shared/PhoneNumber.sys.mjs (renamed from toolkit/components/formautofill/phonenumberutils/PhoneNumber.sys.mjs)4
-rw-r--r--toolkit/components/formautofill/shared/PhoneNumberMetaData.sys.mjs (renamed from toolkit/components/formautofill/phonenumberutils/PhoneNumberMetaData.sys.mjs)0
-rw-r--r--toolkit/components/formautofill/shared/PhoneNumberNormalizer.sys.mjs (renamed from toolkit/components/formautofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs)0
30 files changed, 889 insertions, 733 deletions
diff --git a/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs b/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs
index 2d87f7931d..fc3f0454b0 100644
--- a/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs
+++ b/toolkit/components/formautofill/AutofillProfileAutoComplete.sys.mjs
@@ -349,7 +349,7 @@ export const ProfileAutocomplete = {
Services.obs.removeObserver(this, "autocomplete-will-enter-text");
},
- async observe(subject, topic, data) {
+ async observe(_subject, topic, _data) {
switch (topic) {
case "autocomplete-will-enter-text": {
if (!lazy.FormAutofillContent.activeInput) {
diff --git a/toolkit/components/formautofill/Constants.ios.mjs b/toolkit/components/formautofill/Constants.ios.mjs
index b78e47198d..290e690ea6 100644
--- a/toolkit/components/formautofill/Constants.ios.mjs
+++ b/toolkit/components/formautofill/Constants.ios.mjs
@@ -17,7 +17,7 @@ const IOS_DEFAULT_PREFERENCES = {
"browser.search.region": "US",
"extensions.formautofill.creditCards.supportedCountries": "US,CA,GB,FR,DE",
"extensions.formautofill.addresses.enabled": true,
- "extensions.formautofill.addresses.experiments.enabled": false, // TODO(FXCM-765): fetch this value from swift
+ "extensions.formautofill.addresses.experiments.enabled": true,
"extensions.formautofill.addresses.capture.enabled": false,
"extensions.formautofill.addresses.supportedCountries": "",
"extensions.formautofill.creditCards.enabled": true,
@@ -31,6 +31,7 @@ const IOS_DEFAULT_PREFERENCES = {
"extensions.formautofill.heuristics.captureOnFormRemoval": false,
"extensions.formautofill.heuristics.captureOnPageNavigation": false,
"extensions.formautofill.focusOnAutofill": false,
+ "extensions.formautofill.test.ignoreVisibilityCheck": false,
};
// Used Mimic the behavior of .getAutocompleteInfo()
diff --git a/toolkit/components/formautofill/FormAutofill.ios.sys.mjs b/toolkit/components/formautofill/FormAutofill.ios.sys.mjs
index 8e205c16c6..0b87fee30a 100644
--- a/toolkit/components/formautofill/FormAutofill.ios.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofill.ios.sys.mjs
@@ -4,7 +4,7 @@
import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
-FormAutofill.defineLogGetter = (scope, logPrefix) => ({
+FormAutofill.defineLogGetter = (_scope, _logPrefix) => ({
// TODO: Bug 1828405. Explore how logging should be handled.
// Maybe it makes more sense to do it on swift side and have JS just send messages.
info: () => {},
diff --git a/toolkit/components/formautofill/FormAutofill.sys.mjs b/toolkit/components/formautofill/FormAutofill.sys.mjs
index 77502afbbe..8f50aad7bd 100644
--- a/toolkit/components/formautofill/FormAutofill.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofill.sys.mjs
@@ -81,7 +81,9 @@ export const FormAutofill = {
return false;
},
isAutofillAddressesAvailableInCountry(country) {
- return FormAutofill._addressAutofillSupportedCountries.includes(country);
+ return FormAutofill._addressAutofillSupportedCountries.includes(
+ country.toUpperCase()
+ );
},
get isAutofillEnabled() {
return this.isAutofillAddressesEnabled || this.isAutofillCreditCardsEnabled;
diff --git a/toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs b/toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs
index 1aa713b5b7..3183319fd9 100644
--- a/toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillChild.ios.sys.mjs
@@ -6,6 +6,7 @@
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";
import { FormStateManager } from "resource://gre/modules/shared/FormStateManager.sys.mjs";
import { CreditCardRecord } from "resource://gre/modules/shared/CreditCardRecord.sys.mjs";
+import { AddressRecord } from "resource://gre/modules/shared/AddressRecord.sys.mjs";
export class FormAutofillChild {
/**
@@ -77,7 +78,7 @@ export class FormAutofillChild {
this._doIdentifyAutofillFields(element);
}
- onSubmit(evt) {
+ onSubmit(_event) {
if (!this.fieldDetailsManager.activeHandler) {
return;
}
@@ -100,6 +101,17 @@ export class FormAutofillChild {
}
fillFormFields(payload) {
+ // In iOS, we have access only to valid fields (https://github.com/mozilla/application-services/blob/9054db4bb5031881550ceab3448665ef6499a706/components/autofill/src/autofill.udl#L59-L76) for an address;
+ // all additional data must be computed. On Desktop, computed fields are handled in FormAutofillStorageBase.sys.mjs at the time of saving. Ideally, we should centralize
+ // all transformations, computations, and normalization processes within AddressRecord.sys.mjs to maintain a unified implementation across both platforms.
+ // This will be addressed in FXCM-810, aiming to simplify our data representation for both credit cards and addresses.
+ if (
+ FormAutofillUtils.isAddressField(
+ this.fieldDetailsManager.activeFieldDetail?.fieldName
+ )
+ ) {
+ AddressRecord.computeFields(payload);
+ }
this.fieldDetailsManager.activeHandler.autofillFormFields(payload);
}
}
diff --git a/toolkit/components/formautofill/FormAutofillChild.sys.mjs b/toolkit/components/formautofill/FormAutofillChild.sys.mjs
index c40bfddbce..8678a7bd45 100644
--- a/toolkit/components/formautofill/FormAutofillChild.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillChild.sys.mjs
@@ -2,16 +2,36 @@
* 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/. */
+import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
+import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
AutoCompleteChild: "resource://gre/actors/AutoCompleteChild.sys.mjs",
+ AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs",
FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
FormAutofillContent: "resource://autofill/FormAutofillContent.sys.mjs",
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
+ FormStateManager: "resource://gre/modules/shared/FormStateManager.sys.mjs",
+ PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
+ ProfileAutocomplete:
+ "resource://autofill/AutofillProfileAutoComplete.sys.mjs",
setTimeout: "resource://gre/modules/Timer.sys.mjs",
+ FORM_SUBMISSION_REASON: "resource://gre/actors/FormHandlerChild.sys.mjs",
});
+XPCOMUtils.defineLazyPreferenceGetter(
+ lazy,
+ "DELEGATE_AUTOCOMPLETE",
+ "toolkit.autocomplete.delegate",
+ false
+);
+
+const formFillController = Cc[
+ "@mozilla.org/satchel/form-fill-controller;1"
+].getService(Ci.nsIFormFillController);
+
const observer = {
QueryInterface: ChromeUtils.generateQI([
"nsIWebProgressListener",
@@ -31,7 +51,7 @@ const observer = {
formAutofillChild.onPageNavigation();
},
- onStateChange(aWebProgress, aRequest, aStateFlags, aStatus) {
+ onStateChange(aWebProgress, aRequest, aStateFlags, _aStatus) {
if (
// if restoring a previously-rendered presentation (bfcache)
aStateFlags & Ci.nsIWebProgressListener.STATE_RESTORING &&
@@ -77,21 +97,34 @@ export class FormAutofillChild extends JSWindowActorChild {
constructor() {
super();
+ this.log = lazy.FormAutofill.defineLogGetter(this, "FormAutofillChild");
+ this.debug("init");
+
this._nextHandleElement = null;
- this._alreadyDOMContentLoaded = false;
this._hasDOMContentLoadedHandler = false;
this._hasPendingTask = false;
- this.testListener = null;
+
+ // Flag indicating whether the form is waiting to be filled by Autofill.
+ this._autofillPending = false;
+
+ /**
+ * @type {FormAutofillFieldDetailsManager} handling state management of current forms and handlers.
+ */
+ this._fieldDetailsManager = new lazy.FormStateManager(
+ this.formSubmitted.bind(this),
+ this.formAutofilled.bind(this)
+ );
lazy.AutoCompleteChild.addPopupStateListener(this);
}
didDestroy() {
+ this._fieldDetailsManager.didDestroy();
+
lazy.AutoCompleteChild.removePopupStateListener(this);
- lazy.FormAutofillContent.didDestroy();
}
- popupStateChanged(messageName, data, target) {
+ popupStateChanged(messageName, data, _target) {
let docShell;
try {
docShell = this.docShell;
@@ -108,50 +141,56 @@ export class FormAutofillChild extends JSWindowActorChild {
switch (messageName) {
case "FormAutoComplete:PopupClosed": {
- lazy.FormAutofillContent.onPopupClosed(data.selectedRowStyle);
+ this.onPopupClosed(data.selectedRowStyle);
Services.tm.dispatchToMainThread(() => {
- chromeEventHandler.removeEventListener(
- "keydown",
- lazy.FormAutofillContent._onKeyDown,
- true
- );
+ chromeEventHandler.removeEventListener("keydown", this, true);
});
break;
}
case "FormAutoComplete:PopupOpened": {
- lazy.FormAutofillContent.onPopupOpened();
- chromeEventHandler.addEventListener(
- "keydown",
- lazy.FormAutofillContent._onKeyDown,
- true
- );
+ this.onPopupOpened();
+ chromeEventHandler.addEventListener("keydown", this, true);
break;
}
}
}
/**
- * Invokes the FormAutofillContent to identify the autofill fields
- * and consider opening the dropdown menu for the focused field
- *
+ * Identifies and marks each autofill field
*/
- _doIdentifyAutofillFields() {
+ identifyAutofillFields() {
if (this._hasPendingTask) {
return;
}
this._hasPendingTask = true;
lazy.setTimeout(() => {
- const isAnyFieldIdentified =
- lazy.FormAutofillContent.identifyAutofillFields(
- this._nextHandleElement
- );
- if (isAnyFieldIdentified) {
+ const element = this._nextHandleElement;
+ this.debug(
+ `identifyAutofillFields: ${element.ownerDocument.location?.hostname}`
+ );
+
+ if (
+ lazy.DELEGATE_AUTOCOMPLETE ||
+ !lazy.FormAutofillContent.savedFieldNames
+ ) {
+ this.debug("identifyAutofillFields: savedFieldNames are not known yet");
+
+ // Init can be asynchronous because we don't need anything from the parent
+ // at this point.
+ this.sendAsyncMessage("FormAutofill:InitStorage");
+ }
+
+ const validDetails =
+ this._fieldDetailsManager.identifyAutofillFields(element);
+
+ validDetails?.forEach(detail =>
+ this._markAsAutofillField(detail.element)
+ );
+ if (validDetails.length) {
if (lazy.FormAutofill.captureOnFormRemoval) {
- this.registerDOMDocFetchSuccessEventListener(
- this._nextHandleElement.ownerDocument
- );
+ this.registerDOMDocFetchSuccessEventListener();
}
if (lazy.FormAutofill.captureOnPageNavigation) {
this.registerProgressListener();
@@ -163,7 +202,7 @@ export class FormAutofillChild extends JSWindowActorChild {
// This is for testing purpose only which sends a notification to indicate that the
// form has been identified, and ready to open popup.
this.sendAsyncMessage("FormAutofill:FieldsIdentified");
- lazy.FormAutofillContent.updateActiveInput();
+ this.updateActiveInput();
});
}
@@ -192,13 +231,18 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After being notified of a page navigation, we check whether
* the navigated window is the active window or one of its parents
- * (active window = FormAutofillContent.activeHandler.window)
+ * (active window = activeHandler.window)
*
* @returns {boolean} whether the navigation affects the active window
*/
isActiveWindowNavigation() {
- const activeWindow = lazy.FormAutofillContent.activeHandler.window;
+ const activeWindow = lazy.FormAutofillContent.activeHandler?.window;
const navigatedWindow = this.document.defaultView;
+
+ if (!activeWindow || !navigatedWindow) {
+ return false;
+ }
+
const navigatedBrowsingContext =
BrowsingContext.getFromWindow(navigatedWindow);
@@ -218,19 +262,23 @@ export class FormAutofillChild extends JSWindowActorChild {
* Infer a form submission after document is navigated
*/
onPageNavigation() {
- const activeElement =
- lazy.FormAutofillContent.activeFieldDetail?.elementWeakRef.deref();
-
if (!this.isActiveWindowNavigation()) {
return;
}
- const formSubmissionReason =
- lazy.FormAutofillUtils.FORM_SUBMISSION_REASON.PAGE_NAVIGATION;
+ // TODO: We should not use FormAutofillContent and let the
+ // parent decides which child to notify
+ const activeChild = lazy.FormAutofillContent.activeAutofillChild;
+ const activeElement = activeChild.activeFieldDetail?.elementWeakRef.deref();
+ if (!activeElement) {
+ return;
+ }
+
+ const formSubmissionReason = lazy.FORM_SUBMISSION_REASON.PAGE_NAVIGATION;
// We only capture the form of the active field right now,
// this means that we might miss some fields (see bug 1871356)
- lazy.FormAutofillContent.formSubmitted(activeElement, formSubmissionReason);
+ activeChild.formSubmitted(activeElement, formSubmissionReason);
}
/**
@@ -267,11 +315,9 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After a focusin event and after we identify formautofill fields,
* we set up an event listener for the DOMDocFetchSuccess event
- *
- * @param {Document} document The document we want to be notified by of a DOMDocFetchSuccess event
*/
- registerDOMDocFetchSuccessEventListener(document) {
- document.setNotifyFetchSuccess(true);
+ registerDOMDocFetchSuccessEventListener() {
+ this.document.setNotifyFetchSuccess(true);
// Is removed after a DOMDocFetchSuccess event (bug 1864855)
/* eslint-disable mozilla/balanced-listeners */
@@ -284,11 +330,9 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After a DOMDocFetchSuccess event, we register an event listener for the DOMFormRemoved event
- *
- * @param {Document} document The document we want to be notified by of a DOMFormRemoved event
*/
- registerDOMFormRemovedEventListener(document) {
- document.setNotifyFormOrPasswordRemoved(true);
+ registerDOMFormRemovedEventListener() {
+ this.document.setNotifyFormOrPasswordRemoved(true);
// Is removed after a DOMFormRemoved event (bug 1864855)
/* eslint-disable mozilla/balanced-listeners */
@@ -301,11 +345,9 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After a DOMDocFetchSuccess event we remove the DOMDocFetchSuccess event listener
- *
- * @param {Document} document The document we are notified by of a DOMDocFetchSuccess event
*/
- unregisterDOMDocFetchSuccessEventListener(document) {
- document.setNotifyFetchSuccess(false);
+ unregisterDOMDocFetchSuccessEventListener() {
+ this.document.setNotifyFetchSuccess(false);
this.docShell.chromeEventHandler.removeEventListener(
"DOMDocFetchSuccess",
this
@@ -314,11 +356,9 @@ export class FormAutofillChild extends JSWindowActorChild {
/**
* After a DOMFormRemoved event we remove the DOMFormRemoved event listener
- *
- * @param {Document} document The document we are notified by of a DOMFormRemoved event
*/
- unregisterDOMFormRemovedEventListener(document) {
- document.setNotifyFormOrPasswordRemoved(false);
+ unregisterDOMFormRemovedEventListener() {
+ this.document.setNotifyFormOrPasswordRemoved(false);
this.docShell.chromeEventHandler.removeEventListener(
"DOMFormRemoved",
this
@@ -327,11 +367,7 @@ export class FormAutofillChild extends JSWindowActorChild {
shouldIgnoreFormAutofillEvent(event) {
let nodePrincipal = event.target.nodePrincipal;
- return (
- nodePrincipal.isSystemPrincipal ||
- nodePrincipal.isNullPrincipal ||
- nodePrincipal.schemeIs("about")
- );
+ return nodePrincipal.isSystemPrincipal || nodePrincipal.schemeIs("about");
}
handleEvent(evt) {
@@ -342,16 +378,20 @@ export class FormAutofillChild extends JSWindowActorChild {
return;
}
+ if (!this.windowContext) {
+ // !this.windowContext must not be null, because we need the
+ // windowContext and/or docShell to (un)register form submission listeners
+ return;
+ }
+
switch (evt.type) {
- case "focusin": {
- if (lazy.FormAutofill.isAutofillEnabled) {
- this.onFocusIn(evt);
- }
+ case "keydown": {
+ this._onKeyDown(evt);
break;
}
- case "DOMFormBeforeSubmit": {
+ case "focusin": {
if (lazy.FormAutofill.isAutofillEnabled) {
- this.onDOMFormBeforeSubmit(evt);
+ this.onFocusIn(evt);
}
break;
}
@@ -360,7 +400,13 @@ export class FormAutofillChild extends JSWindowActorChild {
break;
}
case "DOMDocFetchSuccess": {
- this.onDOMDocFetchSuccess(evt);
+ this.onDOMDocFetchSuccess();
+ break;
+ }
+ case "form-submission-detected": {
+ if (lazy.FormAutofill.isAutofillEnabled) {
+ this.onFormSubmission(evt);
+ }
break;
}
@@ -371,45 +417,42 @@ export class FormAutofillChild extends JSWindowActorChild {
}
onFocusIn(evt) {
- lazy.FormAutofillContent.updateActiveInput();
+ this.updateActiveInput();
- let element = evt.target;
+ const element = evt.target;
if (!lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(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._nextHandleElement = element;
+ const doc = element.ownerDocument;
+ if (doc.readyState === "loading") {
+ // For auto-focused input, we might receive focus event before document becomes ready.
+ // When this happens, run field identification after receiving `DOMContentLoaded` event
+ if (!this._hasDOMContentLoadedHandler) {
+ this._hasDOMContentLoadedHandler = true;
+ doc.addEventListener(
+ "DOMContentLoaded",
+ () => this.identifyAutofillFields(),
+ { once: true }
+ );
}
- this._alreadyDOMContentLoaded = true;
+ return;
}
- this._doIdentifyAutofillFields();
+ this.identifyAutofillFields();
}
/**
- * Handle the DOMFormBeforeSubmit event.
+ * Handle form-submission-detected event (dispatched by FormHandlerChild)
*
- * @param {Event} evt
+ * @param {CustomEvent} evt form-submission-detected event
*/
- onDOMFormBeforeSubmit(evt) {
- const formElement = evt.target;
-
- const formSubmissionReason =
- lazy.FormAutofillUtils.FORM_SUBMISSION_REASON.FORM_SUBMIT_EVENT;
+ onFormSubmission(evt) {
+ const formElement = evt.detail.form;
+ const formSubmissionReason = evt.detail.reason;
- lazy.FormAutofillContent.formSubmitted(formElement, formSubmissionReason);
+ this.formSubmitted(formElement, formSubmissionReason);
}
/**
@@ -421,14 +464,10 @@ export class FormAutofillChild extends JSWindowActorChild {
* @param {Event} evt DOMFormRemoved
*/
onDOMFormRemoved(evt) {
- const document = evt.composedTarget.ownerDocument;
-
const formSubmissionReason =
- lazy.FormAutofillUtils.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH;
-
- lazy.FormAutofillContent.formSubmitted(evt.target, formSubmissionReason);
+ lazy.FORM_SUBMISSION_REASON.FORM_REMOVAL_AFTER_FETCH;
- this.unregisterDOMFormRemovedEventListener(document);
+ this.formSubmitted(evt.target, formSubmissionReason);
}
/**
@@ -436,15 +475,21 @@ export class FormAutofillChild extends JSWindowActorChild {
*
* Sets up an event listener for the DOMFormRemoved event
* and unregisters the event listener for DOMDocFetchSuccess event.
- *
- * @param {Event} evt DOMDocFetchSuccess
*/
- onDOMDocFetchSuccess(evt) {
- const document = evt.target;
+ onDOMDocFetchSuccess() {
+ this.registerDOMFormRemovedEventListener();
- this.registerDOMFormRemovedEventListener(document);
+ this.unregisterDOMDocFetchSuccessEventListener();
+ }
- this.unregisterDOMDocFetchSuccessEventListener(document);
+ /**
+ * Unregister all listeners that notify of a form submission,
+ * because we just detected and acted on a form submission
+ */
+ unregisterFormSubmissionListeners() {
+ this.unregisterDOMDocFetchSuccessEventListener();
+ this.unregisterDOMFormRemovedEventListener();
+ this.unregisterProgressListener();
}
receiveMessage(message) {
@@ -456,17 +501,284 @@ export class FormAutofillChild extends JSWindowActorChild {
switch (message.name) {
case "FormAutofill:PreviewProfile": {
- lazy.FormAutofillContent.previewProfile(doc);
+ this.previewProfile(doc);
break;
}
case "FormAutofill:ClearForm": {
- lazy.FormAutofillContent.clearForm();
+ this.clearForm();
break;
}
case "FormAutofill:FillForm": {
- lazy.FormAutofillContent.activeHandler.autofillFormFields(message.data);
+ this.activeHandler.autofillFormFields(message.data);
break;
}
}
}
+
+ get activeFieldDetail() {
+ return this._fieldDetailsManager.activeFieldDetail;
+ }
+
+ get activeFormDetails() {
+ return this._fieldDetailsManager.activeFormDetails;
+ }
+
+ get activeInput() {
+ return this._fieldDetailsManager.activeInput;
+ }
+
+ get activeHandler() {
+ return this._fieldDetailsManager.activeHandler;
+ }
+
+ get activeSection() {
+ return this._fieldDetailsManager.activeSection;
+ }
+
+ /**
+ * Handle a form submission and early return when:
+ * 1. In private browsing mode.
+ * 2. Could not map any autofill handler by form element.
+ * 3. Number of filled fields is less than autofill threshold
+ *
+ * @param {HTMLElement} formElement Root element which receives submit event.
+ * @param {string} formSubmissionReason Reason for invoking the form submission
+ * (see options for FORM_SUBMISSION_REASON in FormAutofillUtils))
+ * @param {Window} domWin Content window; passed for unit tests and when
+ * invoked by the FormAutofillSection
+ * @param {object} handler FormAutofillHander, if known by caller
+ */
+ formSubmitted(
+ formElement,
+ formSubmissionReason,
+ domWin = formElement.ownerGlobal,
+ handler = undefined
+ ) {
+ this.debug(`Handling form submission - infered by ${formSubmissionReason}`);
+
+ lazy.AutofillTelemetry.recordFormSubmissionHeuristicCount(
+ formSubmissionReason
+ );
+
+ if (!lazy.FormAutofill.isAutofillEnabled) {
+ this.debug("Form Autofill is disabled");
+ return;
+ }
+
+ // The `domWin` truthiness test is used by unit tests to bypass this check.
+ if (domWin && lazy.PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
+ this.debug("Ignoring submission in a private window");
+ return;
+ }
+
+ handler = handler || this._fieldDetailsManager._getFormHandler(formElement);
+ const records = this._fieldDetailsManager.getRecords(formElement, handler);
+
+ if (!records || !handler) {
+ this.debug("Form element could not map to an existing handler");
+ return;
+ }
+
+ // Unregister the form submission listeners after handling a form submission
+ this.debug("Unregistering form submission listeners");
+ this.unregisterFormSubmissionListeners();
+
+ [records.address, records.creditCard].forEach((rs, idx) => {
+ lazy.AutofillTelemetry.recordSubmittedSectionCount(
+ idx == 0
+ ? lazy.AutofillTelemetry.ADDRESS
+ : lazy.AutofillTelemetry.CREDIT_CARD,
+ rs?.length
+ );
+
+ rs?.forEach(r => {
+ lazy.AutofillTelemetry.recordFormInteractionEvent(
+ "submitted",
+ r.section,
+ {
+ record: r,
+ form: handler.form,
+ }
+ );
+ delete r.section;
+ });
+ });
+
+ this.sendAsyncMessage("FormAutofill:OnFormSubmit", records);
+ }
+
+ formAutofilled() {
+ lazy.FormAutofillContent.showPopup();
+ }
+
+ /**
+ * All active items should be updated according the active element of
+ * `formFillController.focusedInput`. All of them including element,
+ * handler, section, and field detail, can be retrieved by their own getters.
+ *
+ * @param {HTMLElement|null} element The active item should be updated based
+ * on this or `formFillController.focusedInput` will be taken.
+ */
+ updateActiveInput(element) {
+ element = element || formFillController.focusedInput;
+ if (!element) {
+ this.debug("updateActiveElement: no element selected");
+ return;
+ }
+ lazy.FormAutofillContent.updateActiveAutofillChild(this);
+
+ this._fieldDetailsManager.updateActiveInput(element);
+ this.debug("updateActiveElement: checking for popup-on-focus");
+ // We know this element just received focus. If it's a credit card field,
+ // open its popup.
+ if (this._autofillPending) {
+ this.debug("updateActiveElement: skipping check; autofill is imminent");
+ } else if (element.value?.length !== 0) {
+ this.debug(
+ `updateActiveElement: Not opening popup because field is not empty.`
+ );
+ } else {
+ this.debug(
+ "updateActiveElement: checking if empty field is cc-*: ",
+ this.activeFieldDetail?.fieldName
+ );
+
+ if (
+ this.activeFieldDetail?.fieldName?.startsWith("cc-") ||
+ AppConstants.platform === "android"
+ ) {
+ lazy.FormAutofillContent.showPopup();
+ }
+ }
+ }
+
+ set autofillPending(flag) {
+ this.debug("Setting autofillPending to", flag);
+ this._autofillPending = flag;
+ }
+
+ clearForm() {
+ let focusedInput =
+ this.activeInput ||
+ lazy.ProfileAutocomplete._lastAutoCompleteFocusedInput;
+ if (!focusedInput) {
+ return;
+ }
+
+ this.activeSection.clearPopulatedForm();
+
+ let fieldName = this.activeFieldDetail?.fieldName;
+ if (lazy.FormAutofillUtils.isCreditCardField(fieldName)) {
+ lazy.AutofillTelemetry.recordFormInteractionEvent(
+ "cleared",
+ this.activeSection,
+ { fieldName }
+ );
+ }
+ }
+
+ previewProfile(doc) {
+ let docWin = doc.ownerGlobal;
+ let selectedIndex = lazy.ProfileAutocomplete._getSelectedIndex(docWin);
+ let lastAutoCompleteResult =
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
+ let focusedInput = this.activeInput;
+
+ if (
+ selectedIndex === -1 ||
+ !focusedInput ||
+ !lastAutoCompleteResult ||
+ lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile"
+ ) {
+ this.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {});
+
+ lazy.ProfileAutocomplete._clearProfilePreview();
+ } else {
+ let focusedInputDetails = this.activeFieldDetail;
+ let profile = JSON.parse(
+ lastAutoCompleteResult.getCommentAt(selectedIndex)
+ );
+ let allFieldNames = this.activeSection.allFieldNames;
+ let profileFields = allFieldNames.filter(
+ fieldName => !!profile[fieldName]
+ );
+
+ let focusedCategory = lazy.FormAutofillUtils.getCategoryFromFieldName(
+ focusedInputDetails.fieldName
+ );
+ let categories =
+ lazy.FormAutofillUtils.getCategoriesFromFieldNames(profileFields);
+ this.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {
+ focusedCategory,
+ categories,
+ });
+
+ lazy.ProfileAutocomplete._previewSelectedProfile(selectedIndex);
+ }
+ }
+
+ onPopupClosed(selectedRowStyle) {
+ this.debug("Popup has closed.");
+ lazy.ProfileAutocomplete._clearProfilePreview();
+
+ let lastAutoCompleteResult =
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
+ let focusedInput = this.activeInput;
+ if (
+ lastAutoCompleteResult &&
+ this._keyDownEnterForInput &&
+ focusedInput === this._keyDownEnterForInput &&
+ focusedInput ===
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput
+ ) {
+ if (selectedRowStyle == "autofill-footer") {
+ this.sendAsyncMessage("FormAutofill:OpenPreferences");
+ } else if (selectedRowStyle == "autofill-clear-button") {
+ this.clearForm();
+ }
+ }
+ }
+
+ onPopupOpened() {
+ this.debug(
+ "Popup has opened, automatic =",
+ formFillController.passwordPopupAutomaticallyOpened
+ );
+
+ let fieldName = this.activeFieldDetail?.fieldName;
+ if (fieldName && this.activeSection) {
+ lazy.AutofillTelemetry.recordFormInteractionEvent(
+ "popup_shown",
+ this.activeSection,
+ { fieldName }
+ );
+ }
+ }
+
+ _markAsAutofillField(field) {
+ // Since Form Autofill popup is only for input element, any non-Input
+ // element should be excluded here.
+ if (!HTMLInputElement.isInstance(field)) {
+ return;
+ }
+
+ formFillController.markAsAutofillField(field);
+ }
+
+ _onKeyDown(e) {
+ delete this._keyDownEnterForInput;
+ let lastAutoCompleteResult =
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
+ let focusedInput = this.activeInput;
+ if (
+ e.keyCode != e.DOM_VK_RETURN ||
+ !lastAutoCompleteResult ||
+ !focusedInput ||
+ focusedInput !=
+ lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput
+ ) {
+ return;
+ }
+ this._keyDownEnterForInput = focusedInput;
+ }
}
diff --git a/toolkit/components/formautofill/FormAutofillContent.sys.mjs b/toolkit/components/formautofill/FormAutofillContent.sys.mjs
index 133e5e1d0a..c07e0d67b3 100644
--- a/toolkit/components/formautofill/FormAutofillContent.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillContent.sys.mjs
@@ -8,43 +8,18 @@
/* eslint-disable no-use-before-define */
-import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
-import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
-
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
FormAutofill: "resource://autofill/FormAutofill.sys.mjs",
- FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
- PrivateBrowsingUtils: "resource://gre/modules/PrivateBrowsingUtils.sys.mjs",
- FormStateManager: "resource://gre/modules/shared/FormStateManager.sys.mjs",
ProfileAutocomplete:
"resource://autofill/AutofillProfileAutoComplete.sys.mjs",
- AutofillTelemetry: "resource://autofill/AutofillTelemetry.sys.mjs",
});
-XPCOMUtils.defineLazyPreferenceGetter(
- lazy,
- "DELEGATE_AUTOCOMPLETE",
- "toolkit.autocomplete.delegate",
- false
-);
-
const formFillController = Cc[
"@mozilla.org/satchel/form-fill-controller;1"
].getService(Ci.nsIFormFillController);
-function getActorFromWindow(contentWindow, name = "FormAutofill") {
- // In unit tests, contentWindow isn't a real window.
- if (!contentWindow) {
- return null;
- }
-
- return contentWindow.windowGlobalChild
- ? contentWindow.windowGlobalChild.getActor(name)
- : null;
-}
-
/**
* Handles content's interactions for the process.
*
@@ -63,12 +38,6 @@ export var FormAutofillContent = {
*/
_popupPending: false,
- /**
- * @type {boolean} Flag indicating whether the form is waiting to be
- * filled by Autofill.
- */
- _autofillPending: false,
-
init() {
this.log = lazy.FormAutofill.defineLogGetter(this, "FormAutofillContent");
this.debug("init");
@@ -76,11 +45,13 @@ export var FormAutofillContent = {
// eslint-disable-next-line mozilla/balanced-listeners
Services.cpmm.sharedData.addEventListener("change", this);
- let autofillEnabled = Services.cpmm.sharedData.get("FormAutofill:enabled");
+ const autofillEnabled = Services.cpmm.sharedData.get(
+ "FormAutofill:enabled"
+ );
// If storage hasn't be initialized yet autofillEnabled is undefined but we need to ensure
// autocomplete is registered before the focusin so register it in this case as long as the
// pref is true.
- let shouldEnableAutofill =
+ const shouldEnableAutofill =
autofillEnabled === undefined &&
(lazy.FormAutofill.isAutofillAddressesEnabled ||
lazy.FormAutofill.isAutofillCreditCardsEnabled);
@@ -88,120 +59,49 @@ export var FormAutofillContent = {
lazy.ProfileAutocomplete.ensureRegistered();
}
- /**
- * @type {FormAutofillFieldDetailsManager} handling state management of current forms and handlers.
- */
- this._fieldDetailsManager = new lazy.FormStateManager(
- this.formSubmitted.bind(this),
- this._showPopup.bind(this)
- );
+ this.activeAutofillChild = null;
},
get activeFieldDetail() {
- return this._fieldDetailsManager.activeFieldDetail;
+ return this.activeAutofillChild?.activeFieldDetail;
},
get activeFormDetails() {
- return this._fieldDetailsManager.activeFormDetails;
+ return this.activeAutofillChild?.activeFormDetails;
},
get activeInput() {
- return this._fieldDetailsManager.activeInput;
+ return this.activeAutofillChild?.activeInput;
},
get activeHandler() {
- return this._fieldDetailsManager.activeHandler;
+ return this.activeAutofillChild?.activeHandler;
},
get activeSection() {
- return this._fieldDetailsManager.activeSection;
- },
-
- /**
- * Send the profile to parent for doorhanger and storage saving/updating.
- *
- * @param {object} profile Submitted form's address/creditcard guid and record.
- * @param {object} domWin Current content window.
- */
- _onFormSubmit(profile, domWin) {
- let actor = getActorFromWindow(domWin);
- actor.sendAsyncMessage("FormAutofill:OnFormSubmit", profile);
+ return this.activeAutofillChild?.activeSection;
},
- /**
- * Handle a form submission and early return when:
- * 1. In private browsing mode.
- * 2. Could not map any autofill handler by form element.
- * 3. Number of filled fields is less than autofill threshold
- *
- * @param {HTMLElement} formElement Root element which receives submit event.
- * @param {string} formSubmissionReason Reason for invoking the form submission
- * (see options for FORM_SUBMISSION_REASON in FormAutofillUtils))
- * @param {Window} domWin Content window; passed for unit tests and when
- * invoked by the FormAutofillSection
- * @param {object} handler FormAutofillHander, if known by caller
- */
- formSubmitted(
- formElement,
- formSubmissionReason,
- domWin = formElement.ownerGlobal,
- handler = undefined
- ) {
- this.debug(`Handling form submission - infered by ${formSubmissionReason}`);
-
- // Unregister the progress listener since we detected a form submission
- // (domWin is null in unit tests)
- getActorFromWindow(domWin)?.unregisterProgressListener();
-
- lazy.AutofillTelemetry.recordFormSubmissionHeuristicCount(
- formSubmissionReason
- );
-
- if (!lazy.FormAutofill.isAutofillEnabled) {
- this.debug("Form Autofill is disabled");
- return;
- }
-
- // The `domWin` truthiness test is used by unit tests to bypass this check.
- if (domWin && lazy.PrivateBrowsingUtils.isContentWindowPrivate(domWin)) {
- this.debug("Ignoring submission in a private window");
- return;
- }
-
- handler = handler || this._fieldDetailsManager._getFormHandler(formElement);
- const records = this._fieldDetailsManager.getRecords(formElement, handler);
-
- if (!records || !handler) {
- this.debug("Form element could not map to an existing handler");
- return;
+ set autofillPending(flag) {
+ if (this.activeAutofillChild) {
+ this.activeAutofillChild.autofillPending = flag;
}
+ },
- [records.address, records.creditCard].forEach((rs, idx) => {
- lazy.AutofillTelemetry.recordSubmittedSectionCount(
- idx == 0
- ? lazy.AutofillTelemetry.ADDRESS
- : lazy.AutofillTelemetry.CREDIT_CARD,
- rs?.length
- );
-
- rs?.forEach(r => {
- lazy.AutofillTelemetry.recordFormInteractionEvent(
- "submitted",
- r.section,
- {
- record: r,
- form: handler.form,
- }
- );
- delete r.section;
- });
- });
-
- this._onFormSubmit(records, domWin);
+ updateActiveAutofillChild(autofillChild) {
+ this.activeAutofillChild = autofillChild;
},
- _showPopup() {
- formFillController.showPopup();
+ showPopup() {
+ if (Services.cpmm.sharedData.get("FormAutofill:enabled")) {
+ this.debug("updateActiveElement: opening pop up");
+ formFillController.showPopup();
+ } else {
+ this.debug(
+ "updateActiveElement: Deferring pop-up until Autofill is ready"
+ );
+ this._popupPending = true;
+ }
},
handleEvent(evt) {
@@ -215,7 +115,7 @@ export var FormAutofillContent = {
if (this._popupPending) {
this._popupPending = false;
this.debug("handleEvent: Opening deferred popup");
- this._showPopup();
+ formFillController.showPopup();
}
} else {
lazy.ProfileAutocomplete.ensureUnregistered();
@@ -224,219 +124,6 @@ export var FormAutofillContent = {
}
}
},
-
- /**
- * All active items should be updated according the active element of
- * `formFillController.focusedInput`. All of them including element,
- * handler, section, and field detail, can be retrieved by their own getters.
- *
- * @param {HTMLElement|null} element The active item should be updated based
- * on this or `formFillController.focusedInput` will be taken.
- */
- updateActiveInput(element) {
- element = element || formFillController.focusedInput;
- if (!element) {
- this.debug("updateActiveElement: no element selected");
- return;
- }
- this._fieldDetailsManager.updateActiveInput(element);
- this.debug("updateActiveElement: checking for popup-on-focus");
- // We know this element just received focus. If it's a credit card field,
- // open its popup.
- if (this._autofillPending) {
- this.debug("updateActiveElement: skipping check; autofill is imminent");
- } else if (element.value?.length !== 0) {
- this.debug(
- `updateActiveElement: Not opening popup because field is not empty.`
- );
- } else {
- this.debug(
- "updateActiveElement: checking if empty field is cc-*: ",
- this.activeFieldDetail?.fieldName
- );
-
- if (
- this.activeFieldDetail?.fieldName?.startsWith("cc-") ||
- AppConstants.platform === "android"
- ) {
- if (Services.cpmm.sharedData.get("FormAutofill:enabled")) {
- this.debug("updateActiveElement: opening pop up");
- this._showPopup();
- } else {
- this.debug(
- "updateActiveElement: Deferring pop-up until Autofill is ready"
- );
- this._popupPending = true;
- }
- }
- }
- },
-
- set autofillPending(flag) {
- this.debug("Setting autofillPending to", flag);
- this._autofillPending = flag;
- },
-
- /**
- * Identifies and marks each autofill field
- *
- * @param {HTMLElement} element
- * Element that serves as an anchor for the formautofill heuristics to retrieve
- * the root form and run the formautofill heuristics on the form elements
- * @returns {boolean}
- * whether any autofill fields were identified
- */
- identifyAutofillFields(element) {
- this.debug(
- `identifyAutofillFields: ${element.ownerDocument.location?.hostname}`
- );
-
- if (lazy.DELEGATE_AUTOCOMPLETE || !this.savedFieldNames) {
- this.debug("identifyAutofillFields: savedFieldNames are not known yet");
- let actor = getActorFromWindow(element.ownerGlobal);
- if (actor) {
- actor.sendAsyncMessage("FormAutofill:InitStorage");
- }
- }
-
- const validDetails =
- this._fieldDetailsManager.identifyAutofillFields(element);
-
- validDetails?.forEach(detail => this._markAsAutofillField(detail.element));
-
- return !!validDetails.length;
- },
-
- clearForm() {
- let focusedInput =
- this.activeInput ||
- lazy.ProfileAutocomplete._lastAutoCompleteFocusedInput;
- if (!focusedInput) {
- return;
- }
-
- this.activeSection.clearPopulatedForm();
-
- let fieldName = FormAutofillContent.activeFieldDetail?.fieldName;
- if (lazy.FormAutofillUtils.isCreditCardField(fieldName)) {
- lazy.AutofillTelemetry.recordFormInteractionEvent(
- "cleared",
- this.activeSection,
- { fieldName }
- );
- }
- },
-
- previewProfile(doc) {
- let docWin = doc.ownerGlobal;
- let selectedIndex = lazy.ProfileAutocomplete._getSelectedIndex(docWin);
- let lastAutoCompleteResult =
- lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
- let focusedInput = this.activeInput;
- let actor = getActorFromWindow(docWin);
-
- if (
- selectedIndex === -1 ||
- !focusedInput ||
- !lastAutoCompleteResult ||
- lastAutoCompleteResult.getStyleAt(selectedIndex) != "autofill-profile"
- ) {
- actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {});
-
- lazy.ProfileAutocomplete._clearProfilePreview();
- } else {
- let focusedInputDetails = this.activeFieldDetail;
- let profile = JSON.parse(
- lastAutoCompleteResult.getCommentAt(selectedIndex)
- );
- let allFieldNames = FormAutofillContent.activeSection.allFieldNames;
- let profileFields = allFieldNames.filter(
- fieldName => !!profile[fieldName]
- );
-
- let focusedCategory = lazy.FormAutofillUtils.getCategoryFromFieldName(
- focusedInputDetails.fieldName
- );
- let categories =
- lazy.FormAutofillUtils.getCategoriesFromFieldNames(profileFields);
- actor.sendAsyncMessage("FormAutofill:UpdateWarningMessage", {
- focusedCategory,
- categories,
- });
-
- lazy.ProfileAutocomplete._previewSelectedProfile(selectedIndex);
- }
- },
-
- onPopupClosed(selectedRowStyle) {
- this.debug("Popup has closed.");
- lazy.ProfileAutocomplete._clearProfilePreview();
-
- let lastAutoCompleteResult =
- lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
- let focusedInput = FormAutofillContent.activeInput;
- if (
- lastAutoCompleteResult &&
- FormAutofillContent._keyDownEnterForInput &&
- focusedInput === FormAutofillContent._keyDownEnterForInput &&
- focusedInput ===
- lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput
- ) {
- if (selectedRowStyle == "autofill-footer") {
- let actor = getActorFromWindow(focusedInput.ownerGlobal);
- actor.sendAsyncMessage("FormAutofill:OpenPreferences");
- } else if (selectedRowStyle == "autofill-clear-button") {
- FormAutofillContent.clearForm();
- }
- }
- },
-
- onPopupOpened() {
- this.debug(
- "Popup has opened, automatic =",
- formFillController.passwordPopupAutomaticallyOpened
- );
-
- let fieldName = FormAutofillContent.activeFieldDetail?.fieldName;
- if (fieldName && this.activeSection) {
- lazy.AutofillTelemetry.recordFormInteractionEvent(
- "popup_shown",
- this.activeSection,
- { fieldName }
- );
- }
- },
-
- _markAsAutofillField(field) {
- // Since Form Autofill popup is only for input element, any non-Input
- // element should be excluded here.
- if (!HTMLInputElement.isInstance(field)) {
- return;
- }
-
- formFillController.markAsAutofillField(field);
- },
-
- _onKeyDown(e) {
- delete FormAutofillContent._keyDownEnterForInput;
- let lastAutoCompleteResult =
- lazy.ProfileAutocomplete.lastProfileAutoCompleteResult;
- let focusedInput = FormAutofillContent.activeInput;
- if (
- e.keyCode != e.DOM_VK_RETURN ||
- !lastAutoCompleteResult ||
- !focusedInput ||
- focusedInput !=
- lazy.ProfileAutocomplete.lastProfileAutoCompleteFocusedInput
- ) {
- return;
- }
- FormAutofillContent._keyDownEnterForInput = focusedInput;
- },
-
- didDestroy() {
- this._fieldDetailsManager.didDestroy();
- },
};
FormAutofillContent.init();
diff --git a/toolkit/components/formautofill/FormAutofillNative.cpp b/toolkit/components/formautofill/FormAutofillNative.cpp
index 57af789861..08c462aa44 100644
--- a/toolkit/components/formautofill/FormAutofillNative.cpp
+++ b/toolkit/components/formautofill/FormAutofillNative.cpp
@@ -200,14 +200,16 @@ enum class CCExpYearParams : uint8_t {
};
struct AutofillParams {
- EnumeratedArray<CCNumberParams, CCNumberParams::Count, double>
+ EnumeratedArray<CCNumberParams, double, size_t(CCNumberParams::Count)>
mCCNumberParams;
- EnumeratedArray<CCNameParams, CCNameParams::Count, double> mCCNameParams;
- EnumeratedArray<CCTypeParams, CCTypeParams::Count, double> mCCTypeParams;
- EnumeratedArray<CCExpParams, CCExpParams::Count, double> mCCExpParams;
- EnumeratedArray<CCExpMonthParams, CCExpMonthParams::Count, double>
+ EnumeratedArray<CCNameParams, double, size_t(CCNameParams::Count)>
+ mCCNameParams;
+ EnumeratedArray<CCTypeParams, double, size_t(CCTypeParams::Count)>
+ mCCTypeParams;
+ EnumeratedArray<CCExpParams, double, size_t(CCExpParams::Count)> mCCExpParams;
+ EnumeratedArray<CCExpMonthParams, double, size_t(CCExpMonthParams::Count)>
mCCExpMonthParams;
- EnumeratedArray<CCExpYearParams, CCExpYearParams::Count, double>
+ EnumeratedArray<CCExpYearParams, double, size_t(CCExpYearParams::Count)>
mCCExpYearParams;
};
@@ -667,13 +669,11 @@ class FormAutofillImpl {
// Array contains regular expressions to match the corresponding
// field. Ex, CC number, CC type, etc.
using RegexStringArray =
- EnumeratedArray<RegexKey, RegexKey::Count, nsCString>;
+ EnumeratedArray<RegexKey, nsCString, size_t(RegexKey::Count)>;
RegexStringArray mRuleMap;
// Array that holds RegexWrapper that created by regex::ffi::regex_new
- using RegexWrapperArray =
- EnumeratedArray<RegexKey, RegexKey::Count,
- RustRegex>;
+ using RegexWrapperArray = EnumeratedArray<RegexKey, RustRegex, size_t(RegexKey::Count)>;
RegexWrapperArray mRegexes;
};
diff --git a/toolkit/components/formautofill/FormAutofillParent.sys.mjs b/toolkit/components/formautofill/FormAutofillParent.sys.mjs
index ba0d769906..61c4bd2943 100644
--- a/toolkit/components/formautofill/FormAutofillParent.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillParent.sys.mjs
@@ -5,10 +5,10 @@
/*
* Implements a service used to access storage and communicate with content.
*
- * A "fields" array is used to communicate with FormAutofillContent. Each item
+ * A "fields" array is used to communicate with FormAutofillChild. Each item
* represents a single input field in the content page as well as its
* @autocomplete properties. The schema is as below. Please refer to
- * FormAutofillContent.js for more details.
+ * FormAutofillChild.js for more details.
*
* [
* {
@@ -293,7 +293,7 @@ export class FormAutofillParent extends JSWindowActorParent {
}
/**
- * Handles the message coming from FormAutofillContent.
+ * Handles the message coming from FormAutofillChild.
*
* @param {object} message
* @param {string} message.name The name of the message.
diff --git a/toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs b/toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs
index 591bfc1578..f360be4fa6 100644
--- a/toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillStorageBase.sys.mjs
@@ -130,18 +130,19 @@
*/
import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
+import { AddressRecord } from "resource://gre/modules/shared/AddressRecord.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- AutofillTelemetry: "resource://autofill/AutofillTelemetry.sys.mjs",
+ AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs",
CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
CreditCardRecord: "resource://gre/modules/shared/CreditCardRecord.sys.mjs",
FormAutofillNameUtils:
"resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
OSKeyStore: "resource://gre/modules/OSKeyStore.sys.mjs",
- PhoneNumber: "resource://autofill/phonenumberutils/PhoneNumber.sys.mjs",
+ PhoneNumber: "resource://gre/modules/shared/PhoneNumber.sys.mjs",
});
const CryptoHash = Components.Constructor(
@@ -166,23 +167,6 @@ export const ADDRESS_SCHEMA_VERSION = 1;
// Please talk to the sync team before changing this!
export const CREDIT_CARD_SCHEMA_VERSION = 3;
-const NAME_COMPONENTS = ["given-name", "additional-name", "family-name"];
-
-const STREET_ADDRESS_COMPONENTS = [
- "address-line1",
- "address-line2",
- "address-line3",
-];
-
-const TEL_COMPONENTS = [
- "tel-country-code",
- "tel-national",
- "tel-area-code",
- "tel-local",
- "tel-local-prefix",
- "tel-local-suffix",
-];
-
const VALID_ADDRESS_FIELDS = [
"name",
"organization",
@@ -198,9 +182,9 @@ const VALID_ADDRESS_FIELDS = [
const VALID_ADDRESS_COMPUTED_FIELDS = [
"country-name",
- ...NAME_COMPONENTS,
- ...STREET_ADDRESS_COMPONENTS,
- ...TEL_COMPONENTS,
+ ...AddressRecord.NAME_COMPONENTS,
+ ...AddressRecord.STREET_ADDRESS_COMPONENTS,
+ ...AddressRecord.TEL_COMPONENTS,
];
const VALID_CREDIT_CARD_FIELDS = [
@@ -299,20 +283,18 @@ class AutofillRecords {
});
}
- observe(subject, topic, data) {
- switch (topic) {
- case "formautofill-storage-changed":
- let collectionName = subject.wrappedJSObject.collectionName;
- if (collectionName != this._collectionName) {
- return;
- }
- const telemetryType =
- subject.wrappedJSObject.collectionName == "creditCards"
- ? lazy.AutofillTelemetry.CREDIT_CARD
- : lazy.AutofillTelemetry.ADDRESS;
- const count = this._data.filter(entry => !entry.deleted).length;
- lazy.AutofillTelemetry.recordAutofillProfileCount(telemetryType, count);
- break;
+ observe(subject, topic, _data) {
+ if (topic == "formautofill-storage-changed") {
+ let collectionName = subject.wrappedJSObject.collectionName;
+ if (collectionName != this._collectionName) {
+ return;
+ }
+ const telemetryType =
+ subject.wrappedJSObject.collectionName == "creditCards"
+ ? lazy.AutofillTelemetry.CREDIT_CARD
+ : lazy.AutofillTelemetry.ADDRESS;
+ const count = this._data.filter(entry => !entry.deleted).length;
+ lazy.AutofillTelemetry.recordAutofillProfileCount(telemetryType, count);
}
}
@@ -675,7 +657,7 @@ class AutofillRecords {
// Excluding *-name fields from the sync payload would prevent older devices from
// synchronizing with newer devices. To maintain backward compatibility, keep those deprecated
// ields in the payload, ensuring that older devices can still sync with newer devices.
- const fieldsToKeep = NAME_COMPONENTS;
+ const fieldsToKeep = AddressRecord.NAME_COMPONENTS;
await this._stripComputedFields(clonedRecord, fieldsToKeep);
} else {
this._recordReadProcessor(clonedRecord);
@@ -703,7 +685,7 @@ class AutofillRecords {
await Promise.all(
clonedRecords.map(async record => {
if (rawData) {
- const fieldsToKeep = NAME_COMPONENTS;
+ const fieldsToKeep = AddressRecord.NAME_COMPONENTS;
await this._stripComputedFields(record, fieldsToKeep);
} else {
this._recordReadProcessor(record);
@@ -1398,7 +1380,12 @@ class AutofillRecords {
return hasChanges;
}
- hasChanges |= await this.computeFields(record);
+ const originalNumFields = Object.keys(record).length;
+ await this.computeFields(record);
+ const hasNewComputedFields =
+ Object.keys(record).length != originalNumFields;
+
+ hasChanges |= hasNewComputedFields;
return hasChanges;
}
@@ -1486,36 +1473,36 @@ class AutofillRecords {
}
// An interface to be inherited.
- _recordReadProcessor(record) {}
+ _recordReadProcessor(_record) {}
// An interface to be inherited.
- async computeFields(record) {}
+ async computeFields(_record) {}
/**
* An interface to be inherited to mutate the argument to normalize it.
*
- * @param {object} partialRecord containing the record passed by the consumer of
+ * @param {object} _partialRecord containing the record passed by the consumer of
* storage and in the case of `update` with
* `preserveOldProperties` will only include the
* properties that the user is changing so the
* lack of a field doesn't mean that the record
* won't have that field.
*/
- _normalizeFields(partialRecord) {}
+ _normalizeFields(_partialRecord) {}
/**
* An interface to be inherited to validate that the complete record is
* consistent and isn't missing required fields. Overrides should throw for
* invalid records.
*
- * @param {object} record containing the complete record that would be stored
+ * @param {object} _record containing the complete record that would be stored
* if this doesn't throw due to an error.
* @throws
*/
- _validateFields(record) {}
+ _validateFields(_record) {}
// An interface to be inherited.
- migrateRemoteRecord(remoteRecord) {}
+ migrateRemoteRecord(_remoteRecord) {}
}
export class AddressesBase extends AutofillRecords {
@@ -1578,99 +1565,9 @@ export class AddressesBase extends AutofillRecords {
// NOTE: Computed fields should be always present in the storage no matter
// it's empty or not.
- let hasNewComputedFields = false;
-
- if (address.deleted) {
- return hasNewComputedFields;
- }
-
- // Compute split names
- if (!("given-name" in address)) {
- const nameParts = lazy.FormAutofillNameUtils.splitName(address.name);
- address["given-name"] = nameParts.given;
- address["additional-name"] = nameParts.middle;
- address["family-name"] = nameParts.family;
- hasNewComputedFields = true;
- }
-
- // Compute address lines
- if (!("address-line1" in address)) {
- let streetAddress = [];
- if (address["street-address"]) {
- streetAddress = address["street-address"]
- .split("\n")
- .map(s => s.trim());
- }
- for (let i = 0; i < 3; i++) {
- address[`address-line${i + 1}`] = streetAddress[i] || "";
- }
- if (streetAddress.length > 3) {
- address["address-line3"] = lazy.FormAutofillUtils.toOneLineAddress(
- streetAddress.slice(2)
- );
- }
- hasNewComputedFields = true;
- }
-
- // Compute country name
- if (!("country-name" in address)) {
- if (address.country) {
- try {
- address["country-name"] = Services.intl.getRegionDisplayNames(
- undefined,
- [address.country]
- );
- } catch (e) {
- address["country-name"] = "";
- }
- } else {
- address["country-name"] = "";
- }
- hasNewComputedFields = true;
+ if (!address.deleted) {
+ AddressRecord.computeFields(address);
}
-
- // Compute tel
- if (!("tel-national" in address)) {
- if (address.tel) {
- let tel = lazy.PhoneNumber.Parse(
- address.tel,
- address.country || FormAutofill.DEFAULT_REGION
- );
- if (tel) {
- if (tel.countryCode) {
- address["tel-country-code"] = tel.countryCode;
- }
- if (tel.nationalNumber) {
- address["tel-national"] = tel.nationalNumber;
- }
-
- // PhoneNumberUtils doesn't support parsing the components of a telephone
- // number so we hard coded the parser for US numbers only. We will need
- // to figure out how to parse numbers from other regions when we support
- // new countries in the future.
- if (tel.nationalNumber && tel.countryCode == "+1") {
- let telComponents = tel.nationalNumber.match(
- /(\d{3})((\d{3})(\d{4}))$/
- );
- if (telComponents) {
- address["tel-area-code"] = telComponents[1];
- address["tel-local"] = telComponents[2];
- address["tel-local-prefix"] = telComponents[3];
- address["tel-local-suffix"] = telComponents[4];
- }
- }
- } else {
- // Treat "tel" as "tel-national" directly if it can't be parsed.
- address["tel-national"] = address.tel;
- }
- }
-
- TEL_COMPONENTS.forEach(c => {
- address[c] = address[c] || "";
- });
- }
-
- return hasNewComputedFields;
}
_normalizeFields(address) {
@@ -1700,7 +1597,7 @@ export class AddressesBase extends AutofillRecords {
}
_normalizeAddressFields(address) {
- if (STREET_ADDRESS_COMPONENTS.some(c => !!address[c])) {
+ if (AddressRecord.STREET_ADDRESS_COMPONENTS.some(c => !!address[c])) {
// Treat "street-address" as "address-line1" if it contains only one line
// and "address-line1" is omitted.
if (
@@ -1714,14 +1611,14 @@ export class AddressesBase extends AutofillRecords {
// Concatenate "address-line*" if "street-address" is omitted.
if (!address["street-address"]) {
- address["street-address"] = STREET_ADDRESS_COMPONENTS.map(
+ address["street-address"] = AddressRecord.STREET_ADDRESS_COMPONENTS.map(
c => address[c]
)
.join("\n")
.replace(/\n+$/, "");
}
}
- STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
+ AddressRecord.STREET_ADDRESS_COMPONENTS.forEach(c => delete address[c]);
}
_normalizeCountryFields(address) {
@@ -1753,7 +1650,7 @@ export class AddressesBase extends AutofillRecords {
}
_normalizeTelFields(address) {
- if (address.tel || TEL_COMPONENTS.some(c => !!address[c])) {
+ if (address.tel || AddressRecord.TEL_COMPONENTS.some(c => !!address[c])) {
lazy.FormAutofillUtils.compressTel(address);
let possibleRegion = address.country || FormAutofill.DEFAULT_REGION;
@@ -1764,7 +1661,7 @@ export class AddressesBase extends AutofillRecords {
address.tel = tel.internationalNumber;
}
}
- TEL_COMPONENTS.forEach(c => delete address[c]);
+ AddressRecord.TEL_COMPONENTS.forEach(c => delete address[c]);
}
/**
@@ -1793,12 +1690,14 @@ export class AddressesBase extends AutofillRecords {
// we will rebuild it and replace the local `name` field with "Jane Poe".
if (
!("name" in remoteRecord) &&
- NAME_COMPONENTS.some(c => c in remoteRecord)
+ AddressRecord.NAME_COMPONENTS.some(c => c in remoteRecord)
) {
const localRecord = this._findByGUID(remoteRecord.guid);
if (
localRecord &&
- NAME_COMPONENTS.every(c => remoteRecord[c] == localRecord[c])
+ AddressRecord.NAME_COMPONENTS.every(
+ c => remoteRecord[c] == localRecord[c]
+ )
) {
remoteRecord.name = localRecord.name;
} else {
@@ -1815,7 +1714,7 @@ export class AddressesBase extends AutofillRecords {
// This also means that the incoming remote record will also contain *-name fields.
// However, since the autofill storage does not expect remote records to contain
// computed fields while merging, we remove them from the remote record.
- NAME_COMPONENTS.forEach(f => delete remoteRecord[f]);
+ AddressRecord.NAME_COMPONENTS.forEach(f => delete remoteRecord[f]);
}
}
@@ -1879,7 +1778,7 @@ export class CreditCardsBase extends AutofillRecords {
return hasNewComputedFields;
}
- async _encryptNumber(creditCard) {
+ async _encryptNumber(_creditCard) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
diff --git a/toolkit/components/formautofill/FormAutofillSync.sys.mjs b/toolkit/components/formautofill/FormAutofillSync.sys.mjs
index 4540737e38..15ae9b60b5 100644
--- a/toolkit/components/formautofill/FormAutofillSync.sys.mjs
+++ b/toolkit/components/formautofill/FormAutofillSync.sys.mjs
@@ -244,7 +244,7 @@ class AutofillChangeset extends Changeset {
super();
}
- getModifiedTimestamp(id) {
+ getModifiedTimestamp(_id) {
throw new Error("Don't use timestamps to resolve autofill merge conflicts");
}
diff --git a/toolkit/components/formautofill/Helpers.ios.mjs b/toolkit/components/formautofill/Helpers.ios.mjs
index 4144d3e98c..56bb49f0e9 100644
--- a/toolkit/components/formautofill/Helpers.ios.mjs
+++ b/toolkit/components/formautofill/Helpers.ios.mjs
@@ -45,6 +45,12 @@ HTMLElement.prototype.getAutocompleteInfo = function () {
};
};
+// Bug 1835024. Webkit doesn't support `checkVisibility` API
+// https://drafts.csswg.org/cssom-view-1/#dom-element-checkvisibility
+HTMLElement.prototype.checkVisibility = function (_options) {
+ throw new Error(`Not implemented: WebKit doesn't support checkVisibility `);
+};
+
// This function helps us debug better when an error occurs because a certain mock is missing
const withNotImplementedError = obj =>
new Proxy(obj, {
@@ -58,6 +64,15 @@ const withNotImplementedError = obj =>
},
});
+// This function will create a proxy for each undefined property
+// This is useful when the accessed property name is unkonwn beforehand
+const undefinedProxy = () =>
+ new Proxy(() => {}, {
+ get() {
+ return undefinedProxy();
+ },
+ });
+
// Webpack needs to be able to statically analyze require statements in order to build the dependency graph
// In order to require modules dynamically at runtime, we use require.context() to create a dynamic require
// that is still able to be parsed by Webpack at compile time. The "./" and ".mjs" tells webpack that files
@@ -128,23 +143,10 @@ export const OSKeyStore = withNotImplementedError({
ensureLoggedIn: () => true,
});
-// Checks an element's focusability and accessibility via keyboard navigation
-const checkFocusability = element => {
- return (
- !element.disabled &&
- !element.hidden &&
- element.style.display != "none" &&
- element.tabIndex != "-1"
- );
-};
-
// Define mock for Services
// NOTE: Services is a global so we need to attach it to the window
// eslint-disable-next-line no-shadow
export const Services = withNotImplementedError({
- focus: withNotImplementedError({
- elementIsFocusable: checkFocusability,
- }),
locale: withNotImplementedError({ isAppLocaleRTL: false }),
prefs: withNotImplementedError({ prefIsLocked: () => false }),
strings: withNotImplementedError({
@@ -154,7 +156,64 @@ export const Services = withNotImplementedError({
formatStringFromName: () => "",
}),
}),
- uuid: withNotImplementedError({ generateUUID: () => "" }),
+ telemetry: withNotImplementedError({
+ scalarAdd: (scalarName, scalarValue) => {
+ // For now, we only care about the address form telemetry
+ // TODO(FXCM-935): move address telemetry to Glean so we can remove this
+ // Data format of the sent message is:
+ // {
+ // type: "scalar",
+ // name: "formautofill.addresses.detected_sections_count",
+ // value: Number,
+ // }
+ if (scalarName !== "formautofill.addresses.detected_sections_count") {
+ return;
+ }
+
+ // eslint-disable-next-line no-undef
+ webkit.messageHandlers.addressFormTelemetryMessageHandler.postMessage(
+ JSON.stringify({
+ type: "scalar",
+ object: scalarName,
+ value: scalarValue,
+ })
+ );
+ },
+ recordEvent: (category, method, object, value, extra) => {
+ // For now, we only care about the address form telemetry
+ // TODO(FXCM-935): move address telemetry to Glean so we can remove this
+ // Data format of the sent message is:
+ // {
+ // type: "event",
+ // category: "address",
+ // method: "detected" | "filled" | "filled_modified",
+ // object: "address_form" | "address_form_ext",
+ // value: String,
+ // extra: Any,
+ // }
+ if (category !== "address") {
+ return;
+ }
+
+ // eslint-disable-next-line no-undef
+ webkit.messageHandlers.addressFormTelemetryMessageHandler.postMessage(
+ JSON.stringify({
+ type: "event",
+ category,
+ method,
+ object,
+ value,
+ extra,
+ })
+ );
+ },
+ }),
+ // TODO(FXCM-936): we should use crypto.randomUUID() instead of Services.uuid.generateUUID() in our codebase
+ // Underneath crypto.randomUUID() uses the same implementation as generateUUID()
+ // https://searchfox.org/mozilla-central/rev/d405168c4d3c0fb900a7354ae17bb34e939af996/dom/base/Crypto.cpp#96
+ // The only limitation is that it's not available in insecure contexts, which should be fine for both iOS and Desktop
+ // since we only autofill in secure contexts
+ uuid: withNotImplementedError({ generateUUID: () => crypto.randomUUID() }),
});
window.Services = Services;
@@ -163,15 +222,18 @@ window.Localization = function () {
return { formatValueSync: () => "" };
};
+// For now, we ignore all calls to glean.
+// TODO(FXCM-935): move address telemetry to Glean so we can create a universal mock for glean that
+// dispatches telemetry messages to the iOS.
+window.Glean = {
+ formautofillCreditcards: undefinedProxy(),
+ formautofill: undefinedProxy(),
+};
+
export const windowUtils = withNotImplementedError({
removeManuallyManagedState: () => {},
addManuallyManagedState: () => {},
});
window.windowUtils = windowUtils;
-export const AutofillTelemetry = withNotImplementedError({
- recordFormInteractionEvent: () => {},
- recordDetectedSectionCount: () => {},
-});
-
export { IOSAppConstants as AppConstants } from "resource://gre/modules/shared/Constants.ios.mjs";
diff --git a/toolkit/components/formautofill/Overrides.ios.js b/toolkit/components/formautofill/Overrides.ios.js
index a0023a267c..ae5998992b 100644
--- a/toolkit/components/formautofill/Overrides.ios.js
+++ b/toolkit/components/formautofill/Overrides.ios.js
@@ -7,7 +7,6 @@
// This array defines overrides that webpack will use when bundling the JS on iOS
// in order to load the right modules
const ModuleOverrides = {
- "AutofillTelemetry.sys.mjs": "Helpers.ios.mjs",
"AppConstants.sys.mjs": "Helpers.ios.mjs",
"XPCOMUtils.sys.mjs": "Helpers.ios.mjs",
"Region.sys.mjs": "Helpers.ios.mjs",
diff --git a/toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs b/toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs
index 15fc1a520c..52ed8bed03 100644
--- a/toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs
+++ b/toolkit/components/formautofill/ProfileAutoCompleteResult.sys.mjs
@@ -12,7 +12,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
ChromeUtils.defineLazyGetter(
lazy,
"l10n",
- () => new Localization(["browser/preferences/formAutofill.ftl"], true)
+ () => new Localization(["toolkit/formautofill/formAutofill.ftl"], true)
);
class ProfileAutoCompleteResult {
@@ -100,16 +100,16 @@ class ProfileAutoCompleteResult {
* Get the secondary label based on the focused field name and related field names
* in the same form.
*
- * @param {string} focusedFieldName The field name of the focused input
- * @param {Array<object>} allFieldNames The field names in the same section
- * @param {object} profile The profile providing the labels to show.
+ * @param {string} _focusedFieldName The field name of the focused input
+ * @param {Array<object>} _allFieldNames The field names in the same section
+ * @param {object} _profile The profile providing the labels to show.
* @returns {string} The secondary label
*/
- _getSecondaryLabel(focusedFieldName, allFieldNames, profile) {
+ _getSecondaryLabel(_focusedFieldName, _allFieldNames, _profile) {
return "";
}
- _generateLabels(focusedFieldName, allFieldNames, profiles) {}
+ _generateLabels(_focusedFieldName, _allFieldNames, _profiles) {}
/**
* Get the value of the result at the given index.
@@ -190,19 +190,19 @@ class ProfileAutoCompleteResult {
/**
* Returns true if the value at the given index is removable
*
- * @param {number} index The index of the result to remove
+ * @param {number} _index The index of the result to remove
* @returns {boolean} True if the value is removable
*/
- isRemovableAt(index) {
+ isRemovableAt(_index) {
return false;
}
/**
* Removes a result from the resultset
*
- * @param {number} index The index of the result to remove
+ * @param {number} _index The index of the result to remove
*/
- removeValueAt(index) {
+ removeValueAt(_index) {
// There is no plan to support removing profiles via autocomplete.
}
}
@@ -277,10 +277,19 @@ export class AddressResult extends ProfileAutoCompleteResult {
}
_generateLabels(focusedFieldName, allFieldNames, profiles) {
+ const manageLabel = lazy.l10n.formatValueSync(
+ "autofill-manage-addresses-label"
+ );
+
if (this._isInputAutofilled) {
return [
{ primary: "", secondary: "" }, // Clear button
- { primary: "", secondary: "" }, // Footer
+ // Footer
+ {
+ primary: "",
+ secondary: "",
+ manageLabel,
+ },
];
}
@@ -306,6 +315,10 @@ export class AddressResult extends ProfileAutoCompleteResult {
),
};
});
+
+ const focusedCategory =
+ lazy.FormAutofillUtils.getCategoryFromFieldName(focusedFieldName);
+
// Add an empty result entry for footer. Its content will come from
// the footer binding, so don't assign any value to it.
// The additional properties: categories and focusedCategory are required of
@@ -313,12 +326,11 @@ export class AddressResult extends ProfileAutoCompleteResult {
labels.push({
primary: "",
secondary: "",
+ manageLabel,
categories: lazy.FormAutofillUtils.getCategoriesFromFieldNames(
this._allFieldNames
),
- focusedCategory: lazy.FormAutofillUtils.getCategoryFromFieldName(
- this._focusedFieldName
- ),
+ focusedCategory,
});
return labels;
@@ -385,10 +397,19 @@ export class CreditCardResult extends ProfileAutoCompleteResult {
];
}
+ const manageLabel = lazy.l10n.formatValueSync(
+ "autofill-manage-payment-methods-label"
+ );
+
if (this._isInputAutofilled) {
return [
{ primary: "", secondary: "" }, // Clear button
- { primary: "", secondary: "" }, // Footer
+ // Footer
+ {
+ primary: "",
+ secondary: "",
+ manageLabel,
+ },
];
}
@@ -431,8 +452,17 @@ export class CreditCardResult extends ProfileAutoCompleteResult {
image,
};
});
+
+ const focusedCategory =
+ lazy.FormAutofillUtils.getCategoryFromFieldName(focusedFieldName);
+
// Add an empty result entry for footer.
- labels.push({ primary: "", secondary: "" });
+ labels.push({
+ primary: "",
+ secondary: "",
+ manageLabel,
+ focusedCategory,
+ });
return labels;
}
diff --git a/toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs b/toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs
index 6bb0e991b1..5e737a018b 100644
--- a/toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs
+++ b/toolkit/components/formautofill/android/FormAutofillPrompter.sys.mjs
@@ -34,10 +34,10 @@ export let FormAutofillPrompter = {
},
async promptToSaveAddress(
- browser,
- storage,
- flowId,
- { oldRecord, newRecord }
+ _browser,
+ _storage,
+ _flowId,
+ { _oldRecord, _newRecord }
) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
},
diff --git a/toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs b/toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs
index 0d11880ff5..964be31d06 100644
--- a/toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs
+++ b/toolkit/components/formautofill/android/FormAutofillStorage.sys.mjs
@@ -115,17 +115,17 @@ class Addresses extends AddressesBase {
return super.getSavedFieldNames();
}
- async reconcile(remoteRecord) {
+ async reconcile(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
- async findDuplicateGUID(remoteRecord) {
+ async findDuplicateGUID(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
}
class CreditCards extends CreditCardsBase {
- async _encryptNumber(creditCard) {
+ async _encryptNumber(_creditCard) {
// Don't encrypt or obfuscate for GV, since we don't store or show
// the number. The API has to always provide the original number.
}
@@ -220,11 +220,11 @@ class CreditCards extends CreditCardsBase {
return null;
}
- async reconcile(remoteRecord) {
+ async reconcile(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
- async findDuplicateGUID(remoteRecord) {
+ async findDuplicateGUID(_remoteRecord) {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
}
}
diff --git a/toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs b/toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs
index ecf787137e..f166716de5 100644
--- a/toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs
+++ b/toolkit/components/formautofill/default/FormAutofillPrompter.sys.mjs
@@ -11,7 +11,7 @@ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";
-import { AutofillTelemetry } from "resource://autofill/AutofillTelemetry.sys.mjs";
+import { AutofillTelemetry } from "resource://gre/modules/shared/AutofillTelemetry.sys.mjs";
import { showConfirmation } from "resource://gre/modules/FillHelpers.sys.mjs";
const lazy = {};
@@ -187,7 +187,7 @@ export class AutofillDoorhanger {
renderHeader() {
// Render the header text
- const text = this.header.querySelector(`p`);
+ const text = this.header.querySelector(`h1`);
this.doc.l10n.setAttributes(text, this.ui.header.l10nId);
// Render the menu button
@@ -529,6 +529,8 @@ export class AddressSaveDoorhanger extends AutofillDoorhanger {
//const img = this.doc.createElement("img");
const img = this.doc.createXULElement("image");
img.setAttribute("class", imgClass);
+ // ToDo: provide meaningful alt values (bug 1870155):
+ img.setAttribute("alt", "");
section.appendChild(img);
// Each line is consisted of multiple <span> to form diff style texts
diff --git a/toolkit/components/formautofill/moz.build b/toolkit/components/formautofill/moz.build
index 542fc595e0..d2091dc850 100644
--- a/toolkit/components/formautofill/moz.build
+++ b/toolkit/components/formautofill/moz.build
@@ -15,6 +15,8 @@ EXTRA_JS_MODULES.shared += [
"shared/AddressMetaDataExtension.sys.mjs",
"shared/AddressMetaDataLoader.sys.mjs",
"shared/AddressParser.sys.mjs",
+ "shared/AddressRecord.sys.mjs",
+ "shared/AutofillTelemetry.sys.mjs",
"shared/CreditCardRecord.sys.mjs",
"shared/CreditCardRuleset.sys.mjs",
"shared/FieldScanner.sys.mjs",
@@ -26,6 +28,9 @@ EXTRA_JS_MODULES.shared += [
"shared/FormStateManager.sys.mjs",
"shared/HeuristicsRegExp.sys.mjs",
"shared/LabelUtils.sys.mjs",
+ "shared/PhoneNumber.sys.mjs",
+ "shared/PhoneNumberMetaData.sys.mjs",
+ "shared/PhoneNumberNormalizer.sys.mjs",
]
EXPORTS.mozilla += ["FormAutofillNative.h"]
diff --git a/toolkit/components/formautofill/shared/AddressComponent.sys.mjs b/toolkit/components/formautofill/shared/AddressComponent.sys.mjs
index a849e889b2..40e00b66a0 100644
--- a/toolkit/components/formautofill/shared/AddressComponent.sys.mjs
+++ b/toolkit/components/formautofill/shared/AddressComponent.sys.mjs
@@ -11,9 +11,9 @@ ChromeUtils.defineESModuleGetters(lazy, {
FormAutofillNameUtils:
"resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
- PhoneNumber: "resource://autofill/phonenumberutils/PhoneNumber.sys.mjs",
+ PhoneNumber: "resource://gre/modules/shared/PhoneNumber.sys.mjs",
PhoneNumberNormalizer:
- "resource://autofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs",
+ "resource://gre/modules/shared/PhoneNumberNormalizer.sys.mjs",
});
/**
@@ -201,7 +201,7 @@ class StreetAddress extends AddressField {
super(value, region);
this.#structuredStreetAddress = lazy.AddressParser.parseStreetAddress(
- lazy.AddressParser.replaceControlCharacters(this.userValue, " ")
+ lazy.AddressParser.replaceControlCharacters(this.userValue)
);
}
@@ -491,7 +491,7 @@ class Country extends AddressField {
return this.country_code == other.country_code;
}
- contains(other) {
+ contains(_other) {
return false;
}
@@ -841,7 +841,7 @@ class Email extends AddressField {
);
}
- contains(other) {
+ contains(_other) {
return false;
}
diff --git a/toolkit/components/formautofill/shared/AddressParser.sys.mjs b/toolkit/components/formautofill/shared/AddressParser.sys.mjs
index 5cb76934c1..1d36b71bba 100644
--- a/toolkit/components/formautofill/shared/AddressParser.sys.mjs
+++ b/toolkit/components/formautofill/shared/AddressParser.sys.mjs
@@ -271,7 +271,7 @@ export class AddressParser {
return s?.replace(/[.,\/#!$%\^&\*;:{}=\-_~()]/g, "");
}
- static replaceControlCharacters(s, replace) {
+ static replaceControlCharacters(s) {
return s?.replace(/[\t\n\r]/g, " ");
}
diff --git a/toolkit/components/formautofill/shared/AddressRecord.sys.mjs b/toolkit/components/formautofill/shared/AddressRecord.sys.mjs
new file mode 100644
index 0000000000..599a802dcd
--- /dev/null
+++ b/toolkit/components/formautofill/shared/AddressRecord.sys.mjs
@@ -0,0 +1,119 @@
+/* eslint-disable no-useless-concat */
+/* 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/. */
+
+import { FormAutofillNameUtils } from "resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs";
+import { FormAutofillUtils } from "resource://gre/modules/shared/FormAutofillUtils.sys.mjs";
+import { PhoneNumber } from "resource://gre/modules/shared/PhoneNumber.sys.mjs";
+import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
+
+/**
+ * The AddressRecord class serves to handle and normalize internal address records.
+ * AddressRecord is used for processing and consistent data representation.
+ */
+export class AddressRecord {
+ static NAME_COMPONENTS = ["given-name", "additional-name", "family-name"];
+
+ static STREET_ADDRESS_COMPONENTS = [
+ "address-line1",
+ "address-line2",
+ "address-line3",
+ ];
+ static TEL_COMPONENTS = [
+ "tel-country-code",
+ "tel-national",
+ "tel-area-code",
+ "tel-local",
+ "tel-local-prefix",
+ "tel-local-suffix",
+ ];
+
+ static computeFields(address) {
+ this.#computeNameFields(address);
+ this.#computeAddressLineFields(address);
+ this.#computeCountryFields(address);
+ this.#computeTelFields(address);
+ }
+
+ static #computeNameFields(address) {
+ // Compute split names
+ if (!("given-name" in address)) {
+ const nameParts = FormAutofillNameUtils.splitName(address.name);
+ address["given-name"] = nameParts.given;
+ address["additional-name"] = nameParts.middle;
+ address["family-name"] = nameParts.family;
+ }
+ }
+
+ static #computeAddressLineFields(address) {
+ // Compute address lines
+ if (!("address-line1" in address)) {
+ let streetAddress = [];
+ if (address["street-address"]) {
+ streetAddress = address["street-address"]
+ .split("\n")
+ .map(s => s.trim());
+ }
+ for (let i = 0; i < 3; i++) {
+ address[`address-line${i + 1}`] = streetAddress[i] || "";
+ }
+ if (streetAddress.length > 3) {
+ address["address-line3"] = FormAutofillUtils.toOneLineAddress(
+ streetAddress.slice(2)
+ );
+ }
+ }
+ }
+
+ static #computeCountryFields(address) {
+ // Compute country name
+ if (!("country-name" in address)) {
+ address["country-name"] =
+ FormAutofill.countries.get(address.country) ?? "";
+ }
+ }
+
+ static #computeTelFields(address) {
+ // Compute tel
+ if (!("tel-national" in address)) {
+ if (address.tel) {
+ let tel = PhoneNumber.Parse(
+ address.tel,
+ address.country || FormAutofill.DEFAULT_REGION
+ );
+ if (tel) {
+ if (tel.countryCode) {
+ address["tel-country-code"] = tel.countryCode;
+ }
+ if (tel.nationalNumber) {
+ address["tel-national"] = tel.nationalNumber;
+ }
+
+ // PhoneNumberUtils doesn't support parsing the components of a telephone
+ // number so we hard coded the parser for US numbers only. We will need
+ // to figure out how to parse numbers from other regions when we support
+ // new countries in the future.
+ if (tel.nationalNumber && tel.countryCode == "+1") {
+ let telComponents = tel.nationalNumber.match(
+ /(\d{3})((\d{3})(\d{4}))$/
+ );
+ if (telComponents) {
+ address["tel-area-code"] = telComponents[1];
+ address["tel-local"] = telComponents[2];
+ address["tel-local-prefix"] = telComponents[3];
+ address["tel-local-suffix"] = telComponents[4];
+ }
+ }
+ } else {
+ // Treat "tel" as "tel-national" directly if it can't be parsed.
+ address["tel-national"] = address.tel;
+ }
+ }
+
+ this.TEL_COMPONENTS.forEach(c => {
+ address[c] = address[c] || "";
+ });
+ }
+ }
+}
diff --git a/toolkit/components/formautofill/AutofillTelemetry.sys.mjs b/toolkit/components/formautofill/shared/AutofillTelemetry.sys.mjs
index 93aa99a4b8..6a1fa974cc 100644
--- a/toolkit/components/formautofill/AutofillTelemetry.sys.mjs
+++ b/toolkit/components/formautofill/shared/AutofillTelemetry.sys.mjs
@@ -140,7 +140,7 @@ class AutofillTelemetryBase {
this.recordGleanFormEvent("formFilledModified", section.flowId, extra);
}
- recordFormSubmitted(section, record, form) {
+ recordFormSubmitted(section, record, _form) {
let extra = this.#initFormEventExtra("unavailable");
if (record.guid !== null) {
@@ -185,7 +185,7 @@ class AutofillTelemetryBase {
);
}
- recordGleanFormEvent(eventName, flowId, extra) {
+ recordGleanFormEvent(_eventName, _flowId, _extra) {
throw new Error("Not implemented.");
}
@@ -222,7 +222,7 @@ class AutofillTelemetryBase {
Services.telemetry.recordEvent(this.EVENT_CATEGORY, method, "manage");
}
- recordAutofillProfileCount(count) {
+ recordAutofillProfileCount(_count) {
throw new Error("Not implemented.");
}
@@ -311,7 +311,7 @@ export class AddressTelemetry extends AutofillTelemetryBase {
"tel",
];
- recordGleanFormEvent(eventName, flowId, extra) {
+ recordGleanFormEvent(_eventName, _flowId, _extra) {
// To be implemented when migrating the legacy event address.address_form to Glean
}
diff --git a/toolkit/components/formautofill/shared/FieldScanner.sys.mjs b/toolkit/components/formautofill/shared/FieldScanner.sys.mjs
index 22adfdabe8..2118de3de8 100644
--- a/toolkit/components/formautofill/shared/FieldScanner.sys.mjs
+++ b/toolkit/components/formautofill/shared/FieldScanner.sys.mjs
@@ -2,6 +2,11 @@
* 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/. */
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ FormAutofillUtils: "resource://gre/modules/shared/FormAutofillUtils.sys.mjs",
+});
+
/**
* Represents the detailed information about a form field, including
* the inferred field name, the approach used for inferring, and additional metadata.
@@ -73,6 +78,14 @@ export class FieldDetail {
get sectionName() {
return this.section || this.addressType;
}
+
+ #isVisible = null;
+ get isVisible() {
+ if (this.#isVisible == null) {
+ this.#isVisible = lazy.FormAutofillUtils.isFieldVisible(this.element);
+ }
+ return this.#isVisible;
+ }
}
/**
diff --git a/toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs
index 4ee1fc1fe1..fb96e47cae 100644
--- a/toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs
+++ b/toolkit/components/formautofill/shared/FormAutofillHeuristics.sys.mjs
@@ -182,7 +182,7 @@ export const FormAutofillHeuristics = {
* Return true if there is any field can be recognized in the parser,
* otherwise false.
*/
- _parsePhoneFields(scanner, detail) {
+ _parsePhoneFields(scanner, _fieldDetail) {
let matchingResult;
const GRAMMARS = this.PHONE_FIELD_GRAMMARS;
@@ -277,7 +277,7 @@ export const FormAutofillHeuristics = {
* Return true if there is any field can be recognized in the parser,
* otherwise false.
*/
- _parseStreetAddressFields(scanner, fieldDetail) {
+ _parseStreetAddressFields(scanner, _fieldDetail) {
const INTERESTED_FIELDS = [
"street-address",
"address-line1",
@@ -547,7 +547,9 @@ export const FormAutofillHeuristics = {
* all sections within its field details in the form.
*/
getFormInfo(form) {
- let elements = this.getFormElements(form);
+ const elements = Array.from(form.elements).filter(element =>
+ lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element)
+ );
const scanner = new lazy.FieldScanner(elements, element =>
this.inferFieldInfo(element, elements)
@@ -597,22 +599,6 @@ export const FormAutofillHeuristics = {
},
/**
- * Get focusable form elements that are of credit card or address type
- *
- * @param {HTMLElement} form
- * @returns {Array<HTMLElement>} focusable elements
- */
- getFormElements(form) {
- let elements = Array.from(form.elements).filter(
- element =>
- lazy.FormAutofillUtils.isCreditCardOrAddressFieldType(element) &&
- lazy.FormAutofillUtils.isFieldFocusable(element)
- );
-
- return elements;
- },
-
- /**
* The result is an array contains the sections with its belonging field details.
*
* @param {Array<FieldDetails>} fieldDetails field detail array to be classified
@@ -621,46 +607,54 @@ export const FormAutofillHeuristics = {
_classifySections(fieldDetails) {
let sections = [];
for (let i = 0; i < fieldDetails.length; i++) {
- const fieldName = fieldDetails[i].fieldName;
- const sectionName = fieldDetails[i].sectionName;
-
+ const cur = fieldDetails[i];
const [currentSection] = sections.slice(-1);
- // The section this field might belong to
+ // The section this field might be placed into.
let candidateSection = null;
- // If the field doesn't have a section name, MAYBE put it to the previous
- // section if exists. If the field has a section name, maybe put it to the
- // nearest section that either has the same name or it doesn't has a name.
- // Otherwise, create a new section.
- if (!currentSection || !sectionName) {
+ // Use name group from autocomplete attribute (ex, section-xxx) to look for the section
+ // we might place this field into.
+ // If the field doesn't have a section name, the candidate section is the previous section.
+ if (!currentSection || !cur.sectionName) {
candidateSection = currentSection;
- } else if (sectionName) {
+ } else if (cur.sectionName) {
+ // If the field has a section name, the candidate section is the nearest section that
+ // either shares the same name or lacks a name.
for (let idx = sections.length - 1; idx >= 0; idx--) {
- if (!sections[idx].name || sections[idx].name == sectionName) {
+ if (!sections[idx].name || sections[idx].name == cur.sectionName) {
candidateSection = sections[idx];
break;
}
}
}
- // We got an candidate section to put the field to, check whether the section
- // already has a field with the same field name. If yes, only add the field to when
- // the type of the field might appear multiple times in a row.
if (candidateSection) {
let createNewSection = true;
- if (candidateSection.fieldDetails.find(f => f.fieldName == fieldName)) {
+
+ // We might create a new section instead of placing the field in the candiate section if
+ // the section already has a field with the same field name.
+ // We also check visibility for both the fields with the same field name because we don't
+ // wanht to create a new section for an invisible field.
+ if (
+ candidateSection.fieldDetails.find(
+ f => f.fieldName == cur.fieldName && f.isVisible && cur.isVisible
+ )
+ ) {
+ // For some field type, it is common to have multiple fields in one section, for example,
+ // email. In that case, we will not create a new section even when the candidate section
+ // already has a field with the same field name.
const [lastFieldDetail] = candidateSection.fieldDetails.slice(-1);
- if (lastFieldDetail.fieldName == fieldName) {
- if (MULTI_FIELD_NAMES.includes(fieldName)) {
+ if (lastFieldDetail.fieldName == cur.fieldName) {
+ if (MULTI_FIELD_NAMES.includes(cur.fieldName)) {
createNewSection = false;
- } else if (fieldName in MULTI_N_FIELD_NAMES) {
+ } else if (cur.fieldName in MULTI_N_FIELD_NAMES) {
// This is the heuristic to handle special cases where we can have multiple
// fields in one section, but only if the field has appeared N times in a row.
// For example, websites can use 4 consecutive 4-digit `cc-number` fields
// instead of one 16-digit `cc-number` field.
- const N = MULTI_N_FIELD_NAMES[fieldName];
+ const N = MULTI_N_FIELD_NAMES[cur.fieldName];
if (lastFieldDetail.part) {
// If `part` is set, we have already identified this field can be
// merged previously
@@ -673,7 +667,7 @@ export const FormAutofillHeuristics = {
N == 2 ||
fieldDetails
.slice(i + 1, i + N - 1)
- .every(f => f.fieldName == fieldName)
+ .every(f => f.fieldName == cur.fieldName)
) {
lastFieldDetail.part = 1;
fieldDetails[i].part = 2;
diff --git a/toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs
index 1c7696432a..7bda4c167b 100644
--- a/toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs
+++ b/toolkit/components/formautofill/shared/FormAutofillSection.sys.mjs
@@ -7,7 +7,7 @@ import { FormAutofill } from "resource://autofill/FormAutofill.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
- AutofillTelemetry: "resource://autofill/AutofillTelemetry.sys.mjs",
+ AutofillTelemetry: "resource://gre/modules/shared/AutofillTelemetry.sys.mjs",
CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
FormAutofillNameUtils:
"resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
@@ -79,40 +79,40 @@ export class FormAutofillSection {
* Examine the section is createable for storing the profile. This method
* must be overrided.
*
- * @param {Object} record The record for examining createable
+ * @param {Object} _record The record for examining createable
* @returns {boolean} True for the record is createable, otherwise false
*
*/
- isRecordCreatable(record) {
+ isRecordCreatable(_record) {
throw new TypeError("isRecordCreatable method must be overridden");
}
/**
* Override this method if the profile is needed to apply some transformers.
*
- * @param {object} profile
+ * @param {object} _profile
* A profile should be converted based on the specific requirement.
*/
- applyTransformers(profile) {}
+ applyTransformers(_profile) {}
/**
* Override this method if the profile is needed to be customized for
* previewing values.
*
- * @param {object} profile
+ * @param {object} _profile
* A profile for pre-processing before previewing values.
*/
- preparePreviewProfile(profile) {}
+ preparePreviewProfile(_profile) {}
/**
* Override this method if the profile is needed to be customized for filling
* values.
*
- * @param {object} profile
+ * @param {object} _profile
* A profile for pre-processing before filling values.
* @returns {boolean} Whether the profile should be filled.
*/
- async prepareFillingProfile(profile) {
+ async prepareFillingProfile(_profile) {
return true;
}
@@ -846,6 +846,10 @@ export class FormAutofillAddressSection extends FormAutofillSection {
value =
FormAutofillUtils.getAbbreviatedSubregionName([value, text]) || text;
}
+ } else if (fieldDetail.fieldName == "country") {
+ // This is a temporary fix. Ideally we should have either case-insensitive comparaison of country codes
+ // or handle this elsewhere see Bug 1889234 for more context.
+ value = value.toUpperCase();
}
return value;
}
@@ -884,7 +888,7 @@ export class FormAutofillCreditCardSection extends FormAutofillSection {
}
}
- _handlePageHide(event) {
+ _handlePageHide(_event) {
this.handler.window.removeEventListener(
"pagehide",
this._handlePageHide.bind(this)
diff --git a/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs
index ce10c71ce1..e86f14975c 100644
--- a/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs
+++ b/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs
@@ -192,7 +192,7 @@ FormAutofillUtils = {
getAddressLabel(address) {
// TODO: Implement a smarter way for deciding what to display
// as option text. Possibly improve the algorithm in
- // ProfileAutoCompleteResult.jsm and reuse it here.
+ // ProfileAutoCompleteResult.sys.mjs and reuse it here.
let fieldOrder = [
"name",
"-moz-street-address-one-line", // Street address
@@ -302,20 +302,27 @@ FormAutofillUtils = {
},
/**
- * Determines if an element is focusable
- * and accessible via keyboard navigation or not.
+ * Determines if an element is visually hidden or not.
*
* @param {HTMLElement} element
- *
- * @returns {bool} true if the element is focusable and accessible
+ * @param {boolean} visibilityCheck true to run visiblity check against
+ * element.checkVisibility API. Otherwise, test by only checking
+ * `hidden` and `display` attributes
+ * @returns {boolean} true if the element is visible
*/
- isFieldFocusable(element) {
- return (
- // The Services.focus.elementIsFocusable API considers elements with
- // tabIndex="-1" set as focusable. But since they are not accessible
- // via keyboard navigation we treat them as non-interactive
- Services.focus.elementIsFocusable(element, 0) && element.tabIndex != "-1"
- );
+ isFieldVisible(element, visibilityCheck = true) {
+ if (
+ visibilityCheck &&
+ element.checkVisibility &&
+ !FormAutofillUtils.ignoreVisibilityCheck
+ ) {
+ return element.checkVisibility({
+ checkOpacity: true,
+ checkVisibilityCSS: true,
+ });
+ }
+
+ return !element.hidden && element.style.display != "none";
},
/**
@@ -1127,3 +1134,11 @@ XPCOMUtils.defineLazyPreferenceGetter(
"extensions.formautofill.focusOnAutofill",
true
);
+
+// This is only used for testing
+XPCOMUtils.defineLazyPreferenceGetter(
+ FormAutofillUtils,
+ "ignoreVisibilityCheck",
+ "extensions.formautofill.test.ignoreVisibilityCheck",
+ false
+);
diff --git a/toolkit/components/formautofill/shared/FormStateManager.sys.mjs b/toolkit/components/formautofill/shared/FormStateManager.sys.mjs
index 064b4e5356..7481a5981c 100644
--- a/toolkit/components/formautofill/shared/FormStateManager.sys.mjs
+++ b/toolkit/components/formautofill/shared/FormStateManager.sys.mjs
@@ -150,7 +150,7 @@ export class FormStateManager {
}
didDestroy() {
- this._activeItems = null;
+ this._activeItems = {};
}
}
diff --git a/toolkit/components/formautofill/phonenumberutils/PhoneNumber.sys.mjs b/toolkit/components/formautofill/shared/PhoneNumber.sys.mjs
index 80b5e43acb..5288765181 100644
--- a/toolkit/components/formautofill/phonenumberutils/PhoneNumber.sys.mjs
+++ b/toolkit/components/formautofill/shared/PhoneNumber.sys.mjs
@@ -5,13 +5,13 @@
// This library came from https://github.com/andreasgal/PhoneNumber.js but will
// be further maintained by our own in Form Autofill codebase.
-import { PHONE_NUMBER_META_DATA } from "resource://autofill/phonenumberutils/PhoneNumberMetaData.sys.mjs";
+import { PHONE_NUMBER_META_DATA } from "resource://gre/modules/shared/PhoneNumberMetaData.sys.mjs";
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
PhoneNumberNormalizer:
- "resource://autofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs",
+ "resource://gre/modules/shared/PhoneNumberNormalizer.sys.mjs",
});
export var PhoneNumber = (function (dataBase) {
diff --git a/toolkit/components/formautofill/phonenumberutils/PhoneNumberMetaData.sys.mjs b/toolkit/components/formautofill/shared/PhoneNumberMetaData.sys.mjs
index 3338ce7c16..3338ce7c16 100644
--- a/toolkit/components/formautofill/phonenumberutils/PhoneNumberMetaData.sys.mjs
+++ b/toolkit/components/formautofill/shared/PhoneNumberMetaData.sys.mjs
diff --git a/toolkit/components/formautofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs b/toolkit/components/formautofill/shared/PhoneNumberNormalizer.sys.mjs
index 604eefe314..604eefe314 100644
--- a/toolkit/components/formautofill/phonenumberutils/PhoneNumberNormalizer.sys.mjs
+++ b/toolkit/components/formautofill/shared/PhoneNumberNormalizer.sys.mjs