/** * Provides infrastructure for automated formautofill components tests. */ "use strict"; var { XPCOMUtils } = ChromeUtils.importESModule( "resource://gre/modules/XPCOMUtils.sys.mjs" ); var { ObjectUtils } = ChromeUtils.import( "resource://gre/modules/ObjectUtils.jsm" ); var { FormLikeFactory } = ChromeUtils.importESModule( "resource://gre/modules/FormLikeFactory.sys.mjs" ); var { FormAutofillHandler } = ChromeUtils.importESModule( "resource://gre/modules/shared/FormAutofillHandler.sys.mjs" ); var { AddonTestUtils, MockAsyncShutdown } = ChromeUtils.importESModule( "resource://testing-common/AddonTestUtils.sys.mjs" ); var { ExtensionTestUtils } = ChromeUtils.importESModule( "resource://testing-common/ExtensionXPCShellUtils.sys.mjs" ); var { FileTestUtils } = ChromeUtils.importESModule( "resource://testing-common/FileTestUtils.sys.mjs" ); var { MockDocument } = ChromeUtils.importESModule( "resource://testing-common/MockDocument.sys.mjs" ); var { sinon } = ChromeUtils.importESModule( "resource://testing-common/Sinon.sys.mjs" ); var { TestUtils } = ChromeUtils.importESModule( "resource://testing-common/TestUtils.sys.mjs" ); ChromeUtils.defineESModuleGetters(this, { AddonManager: "resource://gre/modules/AddonManager.sys.mjs", AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs", ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs", FileUtils: "resource://gre/modules/FileUtils.sys.mjs", }); { // We're going to register a mock file source // with region names based on en-US. This is // necessary for tests that expect to match // on region code display names. const fs = [ { path: "toolkit/intl/regionNames.ftl", source: ` region-name-us = United States region-name-nz = New Zealand region-name-au = Australia region-name-ca = Canada region-name-tw = Taiwan `, }, ]; let locales = Services.locale.packagedLocales; const mockSource = L10nFileSource.createMock( "mock", "app", locales, "resource://mock_path", fs ); L10nRegistry.getInstance().registerSources([mockSource]); } do_get_profile(); const EXTENSION_ID = "formautofill@mozilla.org"; AddonTestUtils.init(this); AddonTestUtils.overrideCertDB(); function SetPref(name, value) { switch (typeof value) { case "string": Services.prefs.setCharPref(name, value); break; case "number": Services.prefs.setIntPref(name, value); break; case "boolean": Services.prefs.setBoolPref(name, value); break; default: throw new Error("Unknown type"); } } // Return the current date rounded in the manner that sync does. function getDateForSync() { return Math.round(Date.now() / 10) / 100; } async function loadExtension() { AddonTestUtils.createAppInfo( "xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9.2" ); await AddonTestUtils.promiseStartupManager(); let extensionPath = Services.dirsvc.get("GreD", Ci.nsIFile); extensionPath.append("browser"); extensionPath.append("features"); extensionPath.append(EXTENSION_ID); if (!extensionPath.exists()) { extensionPath.leafName = `${EXTENSION_ID}.xpi`; } let startupPromise = new Promise(resolve => { const { apiManager } = ExtensionParent; function onReady(event, extension) { if (extension.id == EXTENSION_ID) { apiManager.off("ready", onReady); resolve(); } } apiManager.on("ready", onReady); }); await AddonManager.installTemporaryAddon(extensionPath); await startupPromise; } // Returns a reference to a temporary file that is guaranteed not to exist and // is cleaned up later. See FileTestUtils.getTempFile for details. function getTempFile(leafName) { return FileTestUtils.getTempFile(leafName); } async function initProfileStorage( fileName, records, collectionName = "addresses" ) { let { FormAutofillStorage } = ChromeUtils.importESModule( "resource://autofill/FormAutofillStorage.sys.mjs" ); let path = getTempFile(fileName).path; let profileStorage = new FormAutofillStorage(path); await profileStorage.initialize(); // AddonTestUtils inserts its own directory provider that manages TmpD. // It removes that directory at shutdown, which races with shutdown // handing in JSONFile/DeferredTask (which is used by FormAutofillStorage). // Avoid the race by explicitly finalizing any formautofill JSONFile // instances created manually by individual tests when the test finishes. registerCleanupFunction(function finalizeAutofillStorage() { return profileStorage._finalize(); }); if (!records || !Array.isArray(records)) { return profileStorage; } let onChanged = TestUtils.topicObserved( "formautofill-storage-changed", (subject, data) => data == "add" && subject.wrappedJSObject.collectionName == collectionName ); for (let record of records) { Assert.ok(await profileStorage[collectionName].add(record)); await onChanged; } await profileStorage._saveImmediately(); return profileStorage; } function verifySectionAutofillResult(sections, expectedSectionsInfo) { sections.forEach((section, index) => { const expectedSection = expectedSectionsInfo[index]; const fieldDetails = section.fieldDetails; const expectedFieldDetails = expectedSection.fields; info(`verify autofill section[${index}]`); fieldDetails.forEach((field, fieldIndex) => { const expeceted = expectedFieldDetails[fieldIndex]; Assert.equal( expeceted.autofill, field.element.value, `Autofilled value for element(id=${field.element.id}, field name=${field.fieldName}) should be equal` ); }); }); } function verifySectionFieldDetails(sections, expectedSectionsInfo) { sections.forEach((section, index) => { const expectedSection = expectedSectionsInfo[index]; const fieldDetails = section.fieldDetails; const expectedFieldDetails = expectedSection.fields; info(`section[${index}] ${expectedSection.description ?? ""}:`); info(`FieldName Prediction Results: ${fieldDetails.map(i => i.fieldName)}`); info( `FieldName Expected Results: ${expectedFieldDetails.map( detail => detail.fieldName )}` ); Assert.equal( fieldDetails.length, expectedFieldDetails.length, `Expected field count.` ); fieldDetails.forEach((field, fieldIndex) => { const expectedFieldDetail = expectedFieldDetails[fieldIndex]; const expected = { ...{ reason: "autocomplete", section: "", contactType: "", addressType: "", }, ...expectedSection.default, ...expectedFieldDetail, }; const keys = new Set([...Object.keys(field), ...Object.keys(expected)]); ["autofill", "elementWeakRef", "confidence", "part"].forEach(k => keys.delete(k) ); for (const key of keys) { const expectedValue = expected[key]; const actualValue = field[key]; Assert.equal( expectedValue, actualValue, `${key} should be equal, expect ${expectedValue}, got ${actualValue}` ); } }); Assert.equal( section.isValidSection(), !expectedSection.invalid, `Should be an ${expectedSection.invalid ? "invalid" : "valid"} section` ); }); } var FormAutofillHeuristics, LabelUtils; var AddressDataLoader, FormAutofillUtils; function autofillFieldSelector(doc) { return doc.querySelectorAll("input, select"); } /** * Returns the Sync change counter for a profile storage record. Synced records * store additional metadata for tracking changes and resolving merge conflicts. * Deleting a synced record replaces the record with a tombstone. * * @param {AutofillRecords} records * The `AutofillRecords` instance to query. * @param {string} guid * The GUID of the record or tombstone. * @returns {number} * The change counter, or -1 if the record doesn't exist or hasn't * been synced yet. */ function getSyncChangeCounter(records, guid) { let record = records._findByGUID(guid, { includeDeleted: true }); if (!record) { return -1; } let sync = records._getSyncMetaData(record); if (!sync) { return -1; } return sync.changeCounter; } /** * Performs a partial deep equality check to determine if an object contains * the given fields. * * @param {object} object * The object to check. Unlike `ObjectUtils.deepEqual`, properties in * `object` that are not in `fields` will be ignored. * @param {object} fields * The fields to match. * @returns {boolean} * Does `object` contain `fields` with matching values? */ function objectMatches(object, fields) { let actual = {}; for (let key in fields) { if (!object.hasOwnProperty(key)) { return false; } actual[key] = object[key]; } return ObjectUtils.deepEqual(actual, fields); } add_setup(async function head_initialize() { Services.prefs.setBoolPref("extensions.experiments.enabled", true); Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true); Services.prefs.setCharPref( "extensions.formautofill.addresses.supported", "on" ); Services.prefs.setCharPref( "extensions.formautofill.creditCards.supported", "on" ); Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true); Services.prefs.setBoolPref( "extensions.formautofill.creditCards.enabled", true ); // Clean up after every test. registerCleanupFunction(function head_cleanup() { Services.prefs.clearUserPref("extensions.experiments.enabled"); Services.prefs.clearUserPref( "extensions.formautofill.creditCards.supported" ); Services.prefs.clearUserPref("extensions.formautofill.addresses.supported"); Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled"); Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill"); Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled"); Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled"); }); await loadExtension(); }); let OSKeyStoreTestUtils; add_setup(async function os_key_store_setup() { ({ OSKeyStoreTestUtils } = ChromeUtils.importESModule( "resource://testing-common/OSKeyStoreTestUtils.sys.mjs" )); OSKeyStoreTestUtils.setup(); registerCleanupFunction(async function cleanup() { await OSKeyStoreTestUtils.cleanup(); }); });