summaryrefslogtreecommitdiffstats
path: root/browser/extensions/formautofill/test/unit/head.js
blob: e5353833efd42ad488ee695724eada15b4a43ce6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
/**
 * 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();
  });
});