tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit 7db2b87c5f82f50bdccced4123ca6c8e9c45f7ce
parent 2e2bb17d71f8de9b1dc59fc93899e368b065ab4a
Author: Krist Baliev <kbaliev@mozilla.com>
Date:   Mon, 24 Nov 2025 13:12:42 +0000

Bug 1962815 - Correct the address level 1 autofill by using full region name instead of ISO codes by normalizing it in the fill process. r=dimi,NeilDeakin

Differential Revision: https://phabricator.services.mozilla.com/D269198

Diffstat:
Mbrowser/extensions/formautofill/test/browser/browser_autofill_address_textarea.js | 49++++++++++++++++++++++++++++++++++++++++++++++++-
Mbrowser/extensions/formautofill/test/browser/browser_dynamic_form_autocompletion.js | 23+++++++++++++++++++++--
Mtoolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs | 36++++++++++++++++++++++++++++++++++++
Mtoolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs | 47+++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 152 insertions(+), 3 deletions(-)

diff --git a/browser/extensions/formautofill/test/browser/browser_autofill_address_textarea.js b/browser/extensions/formautofill/test/browser/browser_autofill_address_textarea.js @@ -3,6 +3,17 @@ http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; +const FormAutofillSharedUtils = ChromeUtils.importESModule( + "resource://gre/modules/shared/FormAutofillUtils.sys.mjs" +); + +function getFullSubregionName(abbreviated, country) { + return FormAutofillSharedUtils.FormAutofillUtils.getFullSubregionName( + abbreviated, + country + ); +} + const TEST_PROFILE_ADDRESS = { "given-name": "John", "additional-name": "R.", @@ -105,7 +116,10 @@ add_autofill_heuristic_tests([ }, { fieldName: "address-level1", - autofill: TEST_PROFILE_ADDRESS["address-level1"], + autofill: getFullSubregionName( + TEST_PROFILE_ADDRESS["address-level1"], + TEST_PROFILE_ADDRESS.country + ), }, { fieldName: "postal-code", @@ -256,4 +270,37 @@ add_autofill_heuristic_tests([ }, ], }, + { + description: + "(address)maxLength Guard should prevent conversion if full subregion name exceeds element's maxLength.", + // Fixture must contain 3 fields to meet the minimum section threshold. + fixtureData: `<form> + <label>address-level1: <input id="address-level1" autocomplete="address-level1" maxlength="2" autofocus></label> + <label>address-level2: <input id="address-level2" autocomplete="address-level2"></label> + <label>postal-code: <input id="postal-code" autocomplete="postal-code"></label> + </form>`, + profile: TEST_PROFILE_ADDRESS, + expectedResult: [ + { + default: { + reason: "autocomplete", + }, + fields: [ + { + fieldName: "address-level1", + // Expecting transformer to bail out, using original abbreviated value. + autofill: TEST_PROFILE_ADDRESS["address-level1"], + }, + { + fieldName: "address-level2", + autofill: TEST_PROFILE_ADDRESS["address-level2"], + }, + { + fieldName: "postal-code", + autofill: TEST_PROFILE_ADDRESS["postal-code"], + }, + ], + }, + ], + }, ]); diff --git a/browser/extensions/formautofill/test/browser/browser_dynamic_form_autocompletion.js b/browser/extensions/formautofill/test/browser/browser_dynamic_form_autocompletion.js @@ -7,6 +7,16 @@ const { FormAutofill } = ChromeUtils.importESModule( "resource://autofill/FormAutofill.sys.mjs" ); +const FormAutofillSharedUtils = ChromeUtils.importESModule( + "resource://gre/modules/shared/FormAutofillUtils.sys.mjs" +); +const getFullSubregionName = (abbreviated, country) => { + return FormAutofillSharedUtils.FormAutofillUtils.getFullSubregionName( + abbreviated, + country + ); +}; + add_setup(async () => { await SpecialPowers.pushPrefEnv({ set: [ @@ -51,7 +61,13 @@ const expectedFilledAddressFields = { fieldName: "street-address", autofill: TEST_ADDRESS_1["street-address"].replace("\n", " "), }, - { fieldName: "address-level1", autofill: TEST_ADDRESS_1["address-level1"] }, + { + fieldName: "address-level1", + autofill: getFullSubregionName( + TEST_ADDRESS_1["address-level1"], + TEST_ADDRESS_1.country + ), + }, { fieldName: "address-level2", autofill: TEST_ADDRESS_1["address-level2"] }, { fieldName: "postal-code", autofill: TEST_ADDRESS_1["postal-code"] }, ], @@ -228,7 +244,10 @@ add_task( }, { fieldName: "address-level1", - autofill: TEST_ADDRESS_1["address-level1"], + autofill: getFullSubregionName( + TEST_ADDRESS_1["address-level1"], + TEST_ADDRESS_1.country + ), }, { fieldName: "address-level2", diff --git a/toolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillHandler.sys.mjs @@ -1186,9 +1186,45 @@ class ProfileTransformer { this.#creditCardExpiryDateTransformer(); this.#creditCardExpMonthAndYearTransformer(); this.#creditCardNameTransformer(); + this.#addressLevelOneTransformer(); this.#adaptFieldMaxLength(); } + /** + * Replaces an abbreviated address-level1 code (e.g. "B") with the full + * region name (e.g. "Buenos Aires") if the target field is a text input. + */ + #addressLevelOneTransformer() { + const fieldName = "address-level1"; + const fieldDetail = this.getFieldDetailByName(fieldName); + if (!fieldDetail || !FormAutofillUtils.isTextControl(fieldDetail.element)) { + return; + } + + const element = fieldDetail.element; + const abbreviatedValue = this.getField(fieldName); + const country = this.getField("country"); + + const fullSubregionName = FormAutofillUtils.getFullSubregionName( + abbreviatedValue, + country + ); + + if (!fullSubregionName || fullSubregionName === abbreviatedValue) { + return; + } + + // No point in using full subregion name if allowed string length is too small. + if ( + element.maxLength !== -1 && + fullSubregionName.length > element.maxLength + ) { + return; + } + + this.setField(fieldName, fullSubregionName); + } + // This function mostly uses getUpdatedField as it relies on the modified // values of fields from the previous functions. #adaptFieldMaxLength() { diff --git a/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs b/toolkit/components/formautofill/shared/FormAutofillUtils.sys.mjs @@ -854,6 +854,53 @@ FormAutofillUtils = { }, /** + * Attempts to find the full sub-region name from an abbreviated / ISO code, + * using the address metadata for the specified country. + * + * @param {string} abbreviatedValue A short sub-region code (e.g. "B"). + * @param {string} country The country code (e.g. "AR"). + * @returns {string|null} The full sub-region name (e.g. "Buenos Aires") or null. + */ + getFullSubregionName(abbreviatedValue, country) { + if (!abbreviatedValue || !country) { + return null; + } + + const collators = this.getSearchCollators(country); + for (const metadata of this.getCountryAddressDataWithLocales(country)) { + const { + sub_keys: subKeys, + sub_names: subNames, + sub_lnames: subLnames, + sub_isoids: subIsoids, + } = metadata; + if (!subKeys) { + continue; + } + + // Use latin names if available, otherwise use native names. + const targetNames = subLnames || subNames || subKeys; + + // Check if the abbreviatedValue matches an ISO ID (e.g. "B") or a key (which may also be an ISO ID). + let matchIndex = subKeys.findIndex(key => + this.strCompare(abbreviatedValue, key, collators) + ); + + if (matchIndex === -1 && subIsoids) { + matchIndex = subIsoids.findIndex(isoid => + this.strCompare(abbreviatedValue, isoid, collators) + ); + } + + if (matchIndex !== -1 && targetNames.length > matchIndex) { + // Return the full or latin name from the targetNames list at the matching index. + return targetNames[matchIndex]; + } + } + return null; + }, + + /** * Find the option element from select element. * 1. Try to find the locale using the country from address. * 2. First pass try to find exact match.