commit b132bbf91978c10051ad107ae787af74967d7e7d parent 6e525bc44be6aba72ddaac6d1c70c69614d4c4d4 Author: Neil Deakin <neil@mozilla.com> Date: Fri, 19 Dec 2025 13:16:36 +0000 Bug 1963078, when an autofill happens but a field is already filled in, but its value is the same as the autofill value, treat it as filled in, r=dimi,credential-management-reviewers This only happens when the autofill was caused by a dynamic form change, as some sites update the form field and values to the same value, often several times when a form is changed. When a normal autofill happens, we do not highlight values even when the current value happens to be the same as the fill value -- otherwise a subsequent clear action would clear the field the user had manually entered. The FORMS_REPLACING_ALL_FIELDS_ON_INPUT form is also corrected to be the file that was intended to be used for this test. This file is expanded upon to test the issue for this bug. Differential Revision: https://phabricator.services.mozilla.com/D276569 Diffstat:
5 files changed, 41 insertions(+), 11 deletions(-)
diff --git a/browser/extensions/formautofill/test/browser/browser_dynamic_form_autocompletion.js b/browser/extensions/formautofill/test/browser/browser_dynamic_form_autocompletion.js @@ -223,7 +223,7 @@ add_task( await waitForAutofill( browser, - selectorToTriggerAutocompletion, + selectorToTriggerAutocompletion + "-after-form-change", elementValueToVerifyAutofill ); info( @@ -235,7 +235,6 @@ add_task( const expectedAdditionalFieldsNotFilled = { fields: [ { fieldName: "name", autofill: "John R. Smith" }, - { fieldName: "email", autofill: TEST_ADDRESS_1.email }, { fieldName: "tel", autofill: TEST_ADDRESS_1.tel }, { fieldName: "country", autofill: TEST_ADDRESS_1.country }, { @@ -257,6 +256,7 @@ add_task( fieldName: "postal-code", autofill: TEST_ADDRESS_1["postal-code"], }, + { fieldName: "email", autofill: TEST_ADDRESS_1.email }, ], }; const actor = diff --git a/browser/extensions/formautofill/test/browser/head.js b/browser/extensions/formautofill/test/browser/head.js @@ -87,7 +87,9 @@ const FORM_IFRAME_SANDBOXED_URL = const FORMS_WITH_DYNAMIC_FORM_CHANGE = "https://example.org" + HTTP_TEST_PATH + "dynamic_forms.html"; const FORMS_REPLACING_ALL_FIELDS_ON_INPUT = - "https://example.org" + HTTP_TEST_PATH + "dynamic_forms.html"; + "https://example.org" + + HTTP_TEST_PATH + + "dynamic_form_replacing_all_fields.html"; const FORM_WITH_USER_INITIATED_FORM_CHANGE = "https://example.org" + HTTP_TEST_PATH + @@ -1249,11 +1251,21 @@ async function verifyAutofillResult(browser, section, expectedSection) { const expected = expectedFieldDetails[i].autofill ?? ""; await SpecialPowers.spawn(context, [{ expected, selector }], async obj => { const element = content.document.querySelector(obj.selector); + + if (obj.expected) { + Assert.equal( + element.autofillState, + "autofill", + `element ${obj.selector} is highlighted` + ); + } + if (content.HTMLSelectElement.isInstance(element)) { if (!obj.expected) { obj.expected = element.options[0].value; } } + Assert.equal( element.value, obj.expected, diff --git a/browser/extensions/formautofill/test/fixtures/dynamic_form_replacing_all_fields.html b/browser/extensions/formautofill/test/fixtures/dynamic_form_replacing_all_fields.html @@ -46,6 +46,16 @@ newChild.value = "" form.append(newChild); }); + + // Now remove a single field and add a replacement to the end of the form. + const email = document.getElementById("email-node-addition-after-form-change"); + const parent = email.parentNode; + parent.removeChild(email); + + const newEmail = document.createElement("input"); + newEmail.id = "email-node-addition-after-form-change"; + newEmail.value = "timbl@w3.org"; + parent.appendChild(newEmail); }); </script> </html> diff --git a/toolkit/components/formautofill/FormAutofillChild.sys.mjs b/toolkit/components/formautofill/FormAutofillChild.sys.mjs @@ -925,13 +925,13 @@ export class FormAutofillChild extends JSWindowActorChild { this.showPopupIfEmpty(focusedElement, fieldName); } - async fillFields(focusedId, elementIds, profile) { + async fillFields(focusedId, elementIds, profile, isFormChange) { let result = new Map(); let handler; try { Services.obs.notifyObservers(null, "autofill-fill-starting"); handler = this.#getHandlerByElementId(elementIds[0]); - handler.fillFields(focusedId, elementIds, profile); + handler.fillFields(focusedId, elementIds, profile, isFormChange); // Return the autofilled result to the parent. The result // is used by both tests and telemetry. diff --git a/toolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs @@ -460,8 +460,10 @@ export class FormAutofillHandler { * An array of IDs for the elements that should be autofilled. * @param {object} profile * The data profile containing the values to be autofilled into the form fields. + * @param {boolean} isFormChange + * True if this a fill caused by a form change. */ - fillFields(focusedId, elementIds, profile) { + fillFields(focusedId, elementIds, profile, isFormChange) { this.cancelRefillOnSiteClearingFieldsAction(); this.#isAutofillInProgress = true; @@ -480,6 +482,7 @@ export class FormAutofillHandler { element.previewValue = ""; + let filledValue; if (FormAutofillUtils.isTextControl(element)) { // Bug 1687679: Since profile appears to be presentation ready data, we need to utilize the "x-formatted" field // that is generated when presentation ready data doesn't fit into the autofilling element. @@ -503,8 +506,11 @@ export class FormAutofillHandler { element.autofillState == FIELD_STATES.AUTO_FILLED ) { FormAutofillHandler.fillFieldValue(element, value); - this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED); - filledValuesByElement.set(element, value); + filledValue = value; + } else if (isFormChange && element.value == value) { + // If this was a fill caused by a form change, and the value is + // identical to the expected filled value, highlight it anyway. + filledValue = value; } } else if (HTMLSelectElement.isInstance(element)) { const option = this.matchSelectOptions(fieldDetail, profile); @@ -527,10 +533,12 @@ export class FormAutofillHandler { FormAutofillHandler.fillFieldValue(element, option.value); } // Autofill highlight appears regardless if value is changed or not + filledValue = option.value; + } + + if (filledValue) { this.changeFieldState(fieldDetail, FIELD_STATES.AUTO_FILLED); - filledValuesByElement.set(element, option.value); - } else { - continue; + filledValuesByElement.set(element, filledValue); } }