head.js (10938B)
1 /** 2 * Provides infrastructure for automated formautofill components tests. 3 */ 4 5 "use strict"; 6 7 var { XPCOMUtils } = ChromeUtils.importESModule( 8 "resource://gre/modules/XPCOMUtils.sys.mjs" 9 ); 10 var { ObjectUtils } = ChromeUtils.importESModule( 11 "resource://gre/modules/ObjectUtils.sys.mjs" 12 ); 13 var { FormLikeFactory } = ChromeUtils.importESModule( 14 "resource://gre/modules/FormLikeFactory.sys.mjs" 15 ); 16 var { FormAutofillHandler } = ChromeUtils.importESModule( 17 "resource://gre/modules/shared/FormAutofillHandler.sys.mjs" 18 ); 19 var { FormAutofillHeuristics } = ChromeUtils.importESModule( 20 "resource://gre/modules/shared/FormAutofillHeuristics.sys.mjs" 21 ); 22 var { AddonTestUtils, MockAsyncShutdown } = ChromeUtils.importESModule( 23 "resource://testing-common/AddonTestUtils.sys.mjs" 24 ); 25 var { ExtensionTestUtils } = ChromeUtils.importESModule( 26 "resource://testing-common/ExtensionXPCShellUtils.sys.mjs" 27 ); 28 var { FileTestUtils } = ChromeUtils.importESModule( 29 "resource://testing-common/FileTestUtils.sys.mjs" 30 ); 31 var { MockDocument } = ChromeUtils.importESModule( 32 "resource://testing-common/MockDocument.sys.mjs" 33 ); 34 var { sinon } = ChromeUtils.importESModule( 35 "resource://testing-common/Sinon.sys.mjs" 36 ); 37 var { TestUtils } = ChromeUtils.importESModule( 38 "resource://testing-common/TestUtils.sys.mjs" 39 ); 40 41 ChromeUtils.defineESModuleGetters(this, { 42 AddonManager: "resource://gre/modules/AddonManager.sys.mjs", 43 AddonManagerPrivate: "resource://gre/modules/AddonManager.sys.mjs", 44 ExtensionParent: "resource://gre/modules/ExtensionParent.sys.mjs", 45 FileUtils: "resource://gre/modules/FileUtils.sys.mjs", 46 }); 47 48 { 49 // We're going to register a mock file source 50 // with region names based on en-US. This is 51 // necessary for tests that expect to match 52 // on region code display names. 53 const fs = [ 54 { 55 path: "toolkit/intl/regionNames.ftl", 56 source: ` 57 region-name-us = United States 58 region-name-nz = New Zealand 59 region-name-au = Australia 60 region-name-ca = Canada 61 region-name-tw = Taiwan 62 `, 63 }, 64 ]; 65 66 let locales = Services.locale.packagedLocales; 67 const mockSource = L10nFileSource.createMock( 68 "mock", 69 "app", 70 locales, 71 "resource://mock_path", 72 fs 73 ); 74 L10nRegistry.getInstance().registerSources([mockSource]); 75 } 76 77 do_get_profile(); 78 79 const EXTENSION_ID = "formautofill@mozilla.org"; 80 81 AddonTestUtils.init(this); 82 AddonTestUtils.overrideCertDB(); 83 84 function SetPref(name, value) { 85 switch (typeof value) { 86 case "string": 87 Services.prefs.setCharPref(name, value); 88 break; 89 case "number": 90 Services.prefs.setIntPref(name, value); 91 break; 92 case "boolean": 93 Services.prefs.setBoolPref(name, value); 94 break; 95 default: 96 throw new Error("Unknown type"); 97 } 98 } 99 100 // Return the current date rounded in the manner that sync does. 101 function getDateForSync() { 102 return Math.round(Date.now() / 10) / 100; 103 } 104 105 // Returns a reference to a temporary file that is guaranteed not to exist and 106 // is cleaned up later. See FileTestUtils.getTempFile for details. 107 function getTempFile(leafName) { 108 return FileTestUtils.getTempFile(leafName); 109 } 110 111 async function initProfileStorage( 112 fileName, 113 records, 114 collectionName = "addresses" 115 ) { 116 let { FormAutofillStorage } = ChromeUtils.importESModule( 117 "resource://autofill/FormAutofillStorage.sys.mjs" 118 ); 119 let path = getTempFile(fileName).path; 120 let profileStorage = new FormAutofillStorage(path); 121 await profileStorage.initialize(); 122 123 // AddonTestUtils inserts its own directory provider that manages TmpD. 124 // It removes that directory at shutdown, which races with shutdown 125 // handing in JSONFile/DeferredTask (which is used by FormAutofillStorage). 126 // Avoid the race by explicitly finalizing any formautofill JSONFile 127 // instances created manually by individual tests when the test finishes. 128 registerCleanupFunction(function finalizeAutofillStorage() { 129 return profileStorage._finalize(); 130 }); 131 132 if (!records || !Array.isArray(records)) { 133 return profileStorage; 134 } 135 136 let onChanged = TestUtils.topicObserved( 137 "formautofill-storage-changed", 138 (subject, data) => 139 data == "add" && subject.wrappedJSObject.collectionName == collectionName 140 ); 141 for (let record of records) { 142 Assert.ok(await profileStorage[collectionName].add(record)); 143 await onChanged; 144 } 145 await profileStorage._saveImmediately(); 146 return profileStorage; 147 } 148 149 function verifySectionAutofillResult(sections, expectedSectionsInfo) { 150 sections.forEach((section, index) => { 151 const expectedSection = expectedSectionsInfo[index]; 152 153 const fieldDetails = section.fieldDetails; 154 const expectedFieldDetails = expectedSection.fields; 155 156 info(`verify autofill section[${index}]`); 157 158 fieldDetails.forEach((field, fieldIndex) => { 159 const expeceted = expectedFieldDetails[fieldIndex]; 160 161 Assert.equal( 162 expeceted.autofill, 163 field.element.value, 164 `Autofilled value for element(id=${field.element.id}, field name=${field.fieldName}) should be equal` 165 ); 166 }); 167 }); 168 } 169 170 function verifySectionFieldDetails(sections, expectedSectionsInfo) { 171 sections.forEach((section, index) => { 172 const expectedSection = expectedSectionsInfo[index]; 173 174 const fieldDetails = section.fieldDetails; 175 const expectedFieldDetails = expectedSection.fields; 176 177 info(`section[${index}] ${expectedSection.description ?? ""}:`); 178 info(`FieldName Prediction Results: ${fieldDetails.map(i => i.fieldName)}`); 179 info( 180 `FieldName Expected Results: ${expectedFieldDetails.map( 181 detail => detail.fieldName 182 )}` 183 ); 184 Assert.equal( 185 fieldDetails.length, 186 expectedFieldDetails.length, 187 `Expected field count.` 188 ); 189 190 fieldDetails.forEach((field, fieldIndex) => { 191 const expectedFieldDetail = expectedFieldDetails[fieldIndex]; 192 193 const expected = { 194 ...{ 195 reason: "autocomplete", 196 section: "", 197 contactType: "", 198 addressType: "", 199 }, 200 ...expectedSection.default, 201 ...expectedFieldDetail, 202 }; 203 204 const keys = new Set([...Object.keys(field), ...Object.keys(expected)]); 205 ["autofill", "elementWeakRef", "confidence", "part"].forEach(k => 206 keys.delete(k) 207 ); 208 209 for (const key of keys) { 210 const expectedValue = expected[key]; 211 const actualValue = field[key]; 212 Assert.equal( 213 expectedValue, 214 actualValue, 215 `${key} should be equal, expect ${expectedValue}, got ${actualValue}` 216 ); 217 } 218 }); 219 220 Assert.equal( 221 section.isValidSection(), 222 !expectedSection.invalid, 223 `Should be an ${expectedSection.invalid ? "invalid" : "valid"} section` 224 ); 225 }); 226 } 227 228 var LabelUtils; 229 var AddressMetaDataLoader, FormAutofillUtils; 230 231 function autofillFieldSelector(doc) { 232 return doc.querySelectorAll("input, select"); 233 } 234 235 /** 236 * Returns the Sync change counter for a profile storage record. Synced records 237 * store additional metadata for tracking changes and resolving merge conflicts. 238 * Deleting a synced record replaces the record with a tombstone. 239 * 240 * @param {AutofillRecords} records 241 * The `AutofillRecords` instance to query. 242 * @param {string} guid 243 * The GUID of the record or tombstone. 244 * @returns {number} 245 * The change counter, or -1 if the record doesn't exist or hasn't 246 * been synced yet. 247 */ 248 function getSyncChangeCounter(records, guid) { 249 let record = records._findByGUID(guid, { includeDeleted: true }); 250 if (!record) { 251 return -1; 252 } 253 let sync = records._getSyncMetaData(record); 254 if (!sync) { 255 return -1; 256 } 257 return sync.changeCounter; 258 } 259 260 /** 261 * Performs a partial deep equality check to determine if an object contains 262 * the given fields. To ensure the object doesn't contain a property, set the 263 * property of the `fields` object to `undefined` 264 * 265 * @param {object} object 266 * The object to check. Unlike `ObjectUtils.deepEqual`, properties in 267 * `object` that are not in `fields` will be ignored. 268 * @param {object} fields 269 * The fields to match. 270 * @returns {boolean} 271 * Does `object` contain `fields` with matching values? 272 */ 273 function objectMatches(object, fields) { 274 let actual = {}; 275 for (const key in fields) { 276 if (!object.hasOwnProperty(key)) { 277 if (fields[key] != undefined) { 278 return false; 279 } 280 } 281 actual[key] = object[key]; 282 } 283 return ObjectUtils.deepEqual(actual, fields); 284 } 285 286 add_setup(async function head_initialize() { 287 Services.prefs.setBoolPref("extensions.experiments.enabled", true); 288 Services.prefs.setBoolPref("dom.forms.autocomplete.formautofill", true); 289 290 Services.prefs.setCharPref( 291 "extensions.formautofill.addresses.supported", 292 "on" 293 ); 294 Services.prefs.setCharPref( 295 "extensions.formautofill.creditCards.supported", 296 "on" 297 ); 298 Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true); 299 Services.prefs.setBoolPref( 300 "extensions.formautofill.creditCards.enabled", 301 true 302 ); 303 304 // Enable SCOPE_APPLICATION for builtin testing. Default in tests is only SCOPE_PROFILE. 305 const scopes = AddonManager.SCOPE_PROFILE | AddonManager.SCOPE_APPLICATION; 306 Services.prefs.setIntPref("extensions.enabledScopes", scopes); 307 308 // Clean up after every test. 309 registerCleanupFunction(function head_cleanup() { 310 Services.prefs.clearUserPref("extensions.experiments.enabled"); 311 Services.prefs.clearUserPref( 312 "extensions.formautofill.creditCards.supported" 313 ); 314 Services.prefs.clearUserPref("extensions.formautofill.addresses.supported"); 315 Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled"); 316 Services.prefs.clearUserPref("dom.forms.autocomplete.formautofill"); 317 Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled"); 318 Services.prefs.clearUserPref("extensions.formautofill.creditCards.enabled"); 319 Services.prefs.clearUserPref("extensions.enabledScopes"); 320 }); 321 322 AddonTestUtils.createAppInfo( 323 "xpcshell@tests.mozilla.org", 324 "XPCShell", 325 "1", 326 "1.9.2" 327 ); 328 329 // Ensure formautofill builtin is installed. 330 const builtinsConfig = await fetch( 331 "chrome://browser/content/built_in_addons.json" 332 ).then(res => res.json()); 333 334 await AddonTestUtils.overrideBuiltIns({ 335 system: [], 336 builtins: builtinsConfig.builtins.filter( 337 entry => entry.addon_id === EXTENSION_ID 338 ), 339 }); 340 341 await AddonTestUtils.promiseRestartManager(); 342 343 const addon = await AddonManager.getAddonByID(EXTENSION_ID); 344 ok(addon, "Expect formautofill addon to be found"); 345 }); 346 347 let OSKeyStoreTestUtils; 348 add_setup(async function os_key_store_setup() { 349 ({ OSKeyStoreTestUtils } = ChromeUtils.importESModule( 350 "resource://testing-common/OSKeyStoreTestUtils.sys.mjs" 351 )); 352 OSKeyStoreTestUtils.setup(); 353 registerCleanupFunction(async function cleanup() { 354 await OSKeyStoreTestUtils.cleanup(); 355 }); 356 });