Bug 1836438 - P1. Move "name" to VALID_FIELDS and move "*-name" to COMPUTED_FIELDS r=credential-management-reviewers,markh,sgalich

Autofill Storage consists of two parts: Valid Fields and Computed Fields.
Valid Fields should store values provided by users, while Computed Fields are
values we derive from the user-provided data. For example, when a user
enters a "tel" number, we compute "tel-country-code", "tel-national", and
"tel-area-code" based on the "tel" number provided.

However, in the current format, the "name" field is stored as a computed field,
while "given/additional/family" names are stored as valid fields. This approach
can lead to the loss of user input. For instance, if a user enters "Mr. John Doe"
as the name, the current approach strips the "Mr." part and stores"John" as the
given name and "Doe" as the family name. When the next time we autofill
a "name" field, we will then only fill in "John Doe"

This patch moves the "name" field to valid fields to ensure the user's input is always
stored.

Differential Revision: https://phabricator.services.mozilla.com/D197658
This commit is contained in:
Dimi 2024-01-18 07:42:14 +00:00
Родитель ead8d3a654
Коммит 9efc5c7c42
21 изменённых файлов: 1657 добавлений и 366 удалений

Просмотреть файл

@ -17,7 +17,7 @@ async function expectSavedAddresses(expectedAddresses) {
} }
const ADDRESS_FIELD_VALUES = { const ADDRESS_FIELD_VALUES = {
"given-name": "Test User", "given-name": "John",
organization: "Sesame Street", organization: "Sesame Street",
"street-address": "123 Sesame Street", "street-address": "123 Sesame Street",
}; };

Просмотреть файл

@ -30,7 +30,8 @@ add_task(async function test_save_doorhanger_shown_no_profile() {
await focusUpdateSubmitForm(browser, { await focusUpdateSubmitForm(browser, {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": "Test User", "#given-name": "John",
"#family-name": "Doe",
"#organization": "Sesame Street", "#organization": "Sesame Street",
"#street-address": "123 Sesame Street", "#street-address": "123 Sesame Street",
"#tel": "1-345-345-3456", "#tel": "1-345-345-3456",
@ -85,7 +86,8 @@ add_task(
await focusUpdateSubmitForm(browser, { await focusUpdateSubmitForm(browser, {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": "Cena", "#given-name": "John",
"#family-name": "Doe",
"#street-address": TEST_ADDRESS_1["street-address"], "#street-address": TEST_ADDRESS_1["street-address"],
"#country": TEST_ADDRESS_1.country, "#country": TEST_ADDRESS_1.country,
"#email": TEST_ADDRESS_1.email, "#email": TEST_ADDRESS_1.email,

Просмотреть файл

@ -50,6 +50,7 @@ add_task(async function test_do_not_save_invalid_fields() {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": VALID_ADDRESS["given-name"], "#given-name": VALID_ADDRESS["given-name"],
"#family-name": VALID_ADDRESS["family-name"],
"#street-address": VALID_ADDRESS["street-address"], "#street-address": VALID_ADDRESS["street-address"],
"#address-level1": VALID_ADDRESS["address-level1"], "#address-level1": VALID_ADDRESS["address-level1"],
"#address-level2": VALID_ADDRESS["address-level2"], "#address-level2": VALID_ADDRESS["address-level2"],

Просмотреть файл

@ -24,7 +24,8 @@ add_setup(async function () {
add_task(async function test_save_doorhanger_state_invalid() { add_task(async function test_save_doorhanger_state_invalid() {
const DEFAULT = { const DEFAULT = {
"given-name": "Test User", "given-name": "John",
"family-name": "Doe",
organization: "Mozilla", organization: "Mozilla",
"street-address": "123 Sesame Street", "street-address": "123 Sesame Street",
country: "US", country: "US",
@ -53,6 +54,7 @@ add_task(async function test_save_doorhanger_state_invalid() {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": DEFAULT["given-name"], "#given-name": DEFAULT["given-name"],
"#family-name": DEFAULT["family-name"],
"#organization": DEFAULT.organization, "#organization": DEFAULT.organization,
"#street-address": DEFAULT["street-address"], "#street-address": DEFAULT["street-address"],
"#address-level1": TEST.filled["address-level1"], "#address-level1": TEST.filled["address-level1"],
@ -71,7 +73,8 @@ add_task(async function test_save_doorhanger_state_invalid() {
add_task(async function test_save_doorhanger_state_valid() { add_task(async function test_save_doorhanger_state_valid() {
const DEFAULT = { const DEFAULT = {
"given-name": "Test User", "given-name": "John",
"family-name": "Doe",
organization: "Mozilla", organization: "Mozilla",
"street-address": "123 Sesame Street", "street-address": "123 Sesame Street",
country: "US", country: "US",
@ -108,6 +111,7 @@ add_task(async function test_save_doorhanger_state_valid() {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": DEFAULT["given-name"], "#given-name": DEFAULT["given-name"],
"#family-name": DEFAULT["family-name"],
"#organization": DEFAULT.organization, "#organization": DEFAULT.organization,
"#street-address": DEFAULT["street-address"], "#street-address": DEFAULT["street-address"],
"#address-level1": TEST.filled["address-level1"], "#address-level1": TEST.filled["address-level1"],

Просмотреть файл

@ -25,7 +25,8 @@ add_setup(async function () {
add_task(async function test_save_doorhanger_tel_invalid() { add_task(async function test_save_doorhanger_tel_invalid() {
const EXPECTED = [ const EXPECTED = [
{ {
"given-name": "Test User", "given-name": "John",
"family-name": "Doe",
organization: "Mozilla", organization: "Mozilla",
"street-address": "123 Sesame Street", "street-address": "123 Sesame Street",
tel: "", tel: "",
@ -49,7 +50,8 @@ add_task(async function test_save_doorhanger_tel_invalid() {
await focusUpdateSubmitForm(browser, { await focusUpdateSubmitForm(browser, {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": "Test User", "#given-name": "John",
"#family-name": "Doe",
"#organization": "Mozilla", "#organization": "Mozilla",
"#street-address": "123 Sesame Street", "#street-address": "123 Sesame Street",
"#tel": TEST, "#tel": TEST,
@ -69,7 +71,8 @@ add_task(async function test_save_doorhanger_tel_invalid() {
add_task(async function test_save_doorhanger_tel_concatenated() { add_task(async function test_save_doorhanger_tel_concatenated() {
const EXPECTED = [ const EXPECTED = [
{ {
"given-name": "Test User", "given-name": "John",
"family-name": "Doe",
organization: "Mozilla", organization: "Mozilla",
tel: "+15202486621", tel: "+15202486621",
}, },
@ -77,6 +80,7 @@ add_task(async function test_save_doorhanger_tel_concatenated() {
const MARKUP = `<form id="form"> const MARKUP = `<form id="form">
<input id="given-name" autocomplete="given-name"> <input id="given-name" autocomplete="given-name">
<input id="family-name" autocomplete="family-name">
<input id="organization" autocomplete="organization"> <input id="organization" autocomplete="organization">
<input id="tel-country-code" autocomplete="tel-country-code"> <input id="tel-country-code" autocomplete="tel-country-code">
<input id="tel-national" autocomplete="tel-national"> <input id="tel-national" autocomplete="tel-national">
@ -97,7 +101,8 @@ add_task(async function test_save_doorhanger_tel_concatenated() {
await focusUpdateSubmitForm(browser, { await focusUpdateSubmitForm(browser, {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": "Test User", "#given-name": "John",
"#family-name": "Doe",
"#organization": "Mozilla", "#organization": "Mozilla",
"#tel-country-code": "+1", "#tel-country-code": "+1",
"#tel-national": "5202486621", "#tel-national": "5202486621",

Просмотреть файл

@ -153,6 +153,7 @@ add_task(async function test_address_display_in_save_doorhanger() {
{ {
description: "Test submit a form without email and tel fields", description: "Test submit a form without email and tel fields",
form: { form: {
"#given-name": "John",
"#family-name": "Doe", "#family-name": "Doe",
"#organization": "Mozilla", "#organization": "Mozilla",
"#street-address": "123 Sesame Street", "#street-address": "123 Sesame Street",
@ -163,6 +164,7 @@ add_task(async function test_address_display_in_save_doorhanger() {
description: "Test submit a form with email field", description: "Test submit a form with email field",
form: { form: {
"#given-name": "John", "#given-name": "John",
"#family-name": "Doe",
"#organization": "Mozilla", "#organization": "Mozilla",
"#street-address": "123 Sesame Street", "#street-address": "123 Sesame Street",
"#email": "test@mozilla.org", "#email": "test@mozilla.org",

Просмотреть файл

@ -22,7 +22,8 @@ add_task(async function test_save_doorhanger_supported_region() {
await focusUpdateSubmitForm(browser, { await focusUpdateSubmitForm(browser, {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": "John Doe", "#given-name": "John",
"#family-name": "Doe",
"#organization": "Mozilla", "#organization": "Mozilla",
"#street-address": "123 Sesame Street", "#street-address": "123 Sesame Street",
"#country": "US", "#country": "US",
@ -44,7 +45,8 @@ add_task(async function test_save_doorhanger_unsupported_region_from_record() {
await focusUpdateSubmitForm(browser, { await focusUpdateSubmitForm(browser, {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": "John Doe", "#given-name": "John",
"#family-name": "Doe",
"#organization": "Mozilla", "#organization": "Mozilla",
"#street-address": "123 Sesame Street", "#street-address": "123 Sesame Street",
"#country": "DE", "#country": "DE",
@ -70,7 +72,8 @@ add_task(async function test_save_doorhanger_unsupported_region_from_pref() {
await focusUpdateSubmitForm(browser, { await focusUpdateSubmitForm(browser, {
focusSelector: "#given-name", focusSelector: "#given-name",
newValues: { newValues: {
"#given-name": "John Doe", "#given-name": "John",
"#family-name": "Doe",
"#organization": "Mozilla", "#organization": "Mozilla",
"#street-address": "123 Sesame Street", "#street-address": "123 Sesame Street",
}, },

Просмотреть файл

@ -47,7 +47,7 @@ add_task(async function check_switch_autofill_form_popup() {
checkMenuEntries( checkMenuEntries(
[ [
`{"primary":"+13453453456","secondary":"123 Sesame Street."}`, `{"primary":"+13453453456","secondary":"123 Sesame Street."}`,
`{"primary":"","secondary":"","categories":["name","organization","address","tel"],"focusedCategory":"tel"}`, `{"primary":"","secondary":"","categories":["organization","address","tel","name"],"focusedCategory":"tel"}`,
], ],
false false
); );
@ -75,7 +75,7 @@ add_task(async function check_switch_autofill_form_popup_back() {
checkMenuEntries( checkMenuEntries(
[ [
`{"primary":"+13453453456","secondary":"123 Sesame Street."}`, `{"primary":"+13453453456","secondary":"123 Sesame Street."}`,
`{"primary":"","secondary":"","categories":["name","organization","address","tel"],"focusedCategory":"tel"}`, `{"primary":"","secondary":"","categories":["organization","address","tel","name"],"focusedCategory":"tel"}`,
], ],
false false
); );

Просмотреть файл

@ -308,7 +308,8 @@ function getSyncChangeCounter(records, guid) {
/** /**
* Performs a partial deep equality check to determine if an object contains * Performs a partial deep equality check to determine if an object contains
* the given fields. * the given fields. To ensure the object doesn't contain a property, set the
* property of the `fields` object to `undefined`
* *
* @param {object} object * @param {object} object
* The object to check. Unlike `ObjectUtils.deepEqual`, properties in * The object to check. Unlike `ObjectUtils.deepEqual`, properties in
@ -320,9 +321,11 @@ function getSyncChangeCounter(records, guid) {
*/ */
function objectMatches(object, fields) { function objectMatches(object, fields) {
let actual = {}; let actual = {};
for (let key in fields) { for (const key in fields) {
if (!object.hasOwnProperty(key)) { if (!object.hasOwnProperty(key)) {
return false; if (fields[key] != undefined) {
return false;
}
} }
actual[key] = object[key]; actual[key] = object[key];
} }

Просмотреть файл

@ -8,9 +8,7 @@ const TEST_STORE_FILE_NAME = "test-profile.json";
const COLLECTION_NAME = "addresses"; const COLLECTION_NAME = "addresses";
const TEST_ADDRESS_1 = { const TEST_ADDRESS_1 = {
"given-name": "Timothy", name: "Timothy John Berners-Lee",
"additional-name": "John",
"family-name": "Berners-Lee",
organization: "World Wide Web Consortium", organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\nMIT Room 32-G524", "street-address": "32 Vassar Street\nMIT Room 32-G524",
"address-level2": "Cambridge", "address-level2": "Cambridge",
@ -28,8 +26,7 @@ const TEST_ADDRESS_2 = {
}; };
const TEST_ADDRESS_3 = { const TEST_ADDRESS_3 = {
"given-name": "Timothy", name: "Timothy Berners-Lee",
"family-name": "Berners-Lee",
"street-address": "Other Address", "street-address": "Other Address",
"postal-code": "12345", "postal-code": "12345",
}; };
@ -40,7 +37,9 @@ const TEST_ADDRESS_WITH_EMPTY_FIELD = {
}; };
const TEST_ADDRESS_WITH_EMPTY_COMPUTED_FIELD = { const TEST_ADDRESS_WITH_EMPTY_COMPUTED_FIELD = {
name: "", "given-name": "",
"additional-name": "",
"family-name": "",
"address-line1": "", "address-line1": "",
"address-line2": "", "address-line2": "",
"address-line3": "", "address-line3": "",
@ -106,13 +105,18 @@ add_task(async function test_getAll() {
do_check_record_matches(addresses[1], TEST_ADDRESS_2); do_check_record_matches(addresses[1], TEST_ADDRESS_2);
// Check computed fields. // Check computed fields.
Assert.equal(addresses[0].name, "Timothy John Berners-Lee"); Assert.equal(addresses[0]["given-name"], "Timothy");
Assert.equal(addresses[0]["additional-name"], "John");
Assert.equal(addresses[0]["family-name"], "Berners-Lee");
Assert.equal(addresses[0]["address-line1"], "32 Vassar Street"); Assert.equal(addresses[0]["address-line1"], "32 Vassar Street");
Assert.equal(addresses[0]["address-line2"], "MIT Room 32-G524"); Assert.equal(addresses[0]["address-line2"], "MIT Room 32-G524");
// Test with rawData set. // Test with rawData set.
addresses = await profileStorage.addresses.getAll({ rawData: true }); addresses = await profileStorage.addresses.getAll({ rawData: true });
Assert.equal(addresses[0].name, undefined); // For backward-compatibility, we keep *-name fields when `rawData` is true
Assert.equal(addresses[0]["given-name"], "Timothy");
Assert.equal(addresses[0]["additional-name"], "John");
Assert.equal(addresses[0]["family-name"], "Berners-Lee");
Assert.equal(addresses[0]["address-line1"], undefined); Assert.equal(addresses[0]["address-line1"], undefined);
Assert.equal(addresses[0]["address-line2"], undefined); Assert.equal(addresses[0]["address-line2"], undefined);
@ -138,7 +142,10 @@ add_task(async function test_get() {
// Test with rawData set. // Test with rawData set.
address = await profileStorage.addresses.get(guid, { rawData: true }); address = await profileStorage.addresses.get(guid, { rawData: true });
Assert.equal(address.name, undefined); // For backward-compatibility, we keep *-name fields when `rawData` is true
Assert.equal(address["given-name"], "Timothy");
Assert.equal(address["additional-name"], "John");
Assert.equal(address["family-name"], "Berners-Lee");
Assert.equal(address["address-line1"], undefined); Assert.equal(address["address-line1"], undefined);
Assert.equal(address["address-line2"], undefined); Assert.equal(address["address-line2"], undefined);
@ -258,8 +265,7 @@ add_task(async function test_update() {
address = await profileStorage.addresses.get(guid, { rawData: true }); address = await profileStorage.addresses.get(guid, { rawData: true });
Assert.equal(address["given-name"], "Tim"); Assert.equal(address.name, "Tim Berners");
Assert.equal(address["family-name"], "Berners");
Assert.equal(address["street-address"], undefined); Assert.equal(address["street-address"], undefined);
Assert.equal(address["postal-code"], "12345"); Assert.equal(address["postal-code"], "12345");
Assert.notEqual(address.timeLastModified, timeLastModified); Assert.notEqual(address.timeLastModified, timeLastModified);

Просмотреть файл

@ -27,15 +27,16 @@ const ADDRESS_TESTCASES = [
record: { record: {
guid: "test-guid", guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION, version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy", // The cached address-line1 field doesn't align "street-address" but it
name: "John", // The cached name field doesn't align "given-name" but it
// won't be recomputed because the migration isn't invoked. // won't be recomputed because the migration isn't invoked.
"address-line1": "Some Address",
"street-address": "32 Vassar Street",
}, },
expectedResult: { expectedResult: {
guid: "test-guid", guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION, version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy", "address-line1": "Some Address",
name: "John", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -44,14 +45,14 @@ const ADDRESS_TESTCASES = [
record: { record: {
guid: "test-guid", guid: "test-guid",
version: 99, version: 99,
"given-name": "Timothy", "address-line1": "Some Address",
name: "John", "street-address": "32 Vassar Street",
}, },
expectedResult: { expectedResult: {
guid: "test-guid", guid: "test-guid",
version: 99, version: 99,
"given-name": "Timothy", "address-line1": "Some Address",
name: "John", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -60,14 +61,14 @@ const ADDRESS_TESTCASES = [
record: { record: {
guid: "test-guid", guid: "test-guid",
version: 0, version: 0,
"given-name": "Timothy", "address-line1": "Some Address",
name: "John", "street-address": "32 Vassar Street",
}, },
expectedResult: { expectedResult: {
guid: "test-guid", guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION, version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy", "address-line1": "32 Vassar Street",
name: "Timothy", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -75,15 +76,15 @@ const ADDRESS_TESTCASES = [
"The record version is omitted. The migration should be invoked.", "The record version is omitted. The migration should be invoked.",
record: { record: {
guid: "test-guid", guid: "test-guid",
"given-name": "Timothy", "address-line1": "Some Address",
name: "John", "street-address": "32 Vassar Street",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
expectedResult: { expectedResult: {
guid: "test-guid", guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION, version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy", "address-line1": "32 Vassar Street",
name: "Timothy", "street-address": "32 Vassar Street",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
}, },
@ -93,15 +94,15 @@ const ADDRESS_TESTCASES = [
record: { record: {
guid: "test-guid", guid: "test-guid",
version: "ABCDE", version: "ABCDE",
"given-name": "Timothy", "address-line1": "Some Address",
name: "John", "street-address": "32 Vassar Street",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
expectedResult: { expectedResult: {
guid: "test-guid", guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION, version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy", "address-line1": "32 Vassar Street",
name: "Timothy", "street-address": "32 Vassar Street",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
}, },
@ -111,13 +112,13 @@ const ADDRESS_TESTCASES = [
record: { record: {
guid: "test-guid", guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION, version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy", "street-address": "32 Vassar Street",
}, },
expectedResult: { expectedResult: {
guid: "test-guid", guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION, version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy", "address-line1": "32 Vassar Street",
name: "Timothy", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -137,6 +138,98 @@ const ADDRESS_TESTCASES = [
name: undefined, name: undefined,
}, },
}, },
// Bug 1836438 - Migrate "*-name" to "name"
{
description:
"Migrate address - `given-name`, `additional-name`, and `family-name` should be migrated to `name`",
record: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy",
"additional-name": "John",
"family-name": "Berners-Lee",
},
expectedResult: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
name: "Timothy John Berners-Lee",
},
},
{
description: "Migrate address - `given-name` should be migrated to `name`",
record: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy",
},
expectedResult: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
name: "Timothy",
},
},
{
description:
"Migrate address - `additional-name` should be migrated to `name`",
record: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
"additional-name": "John",
},
expectedResult: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
name: "John",
},
},
{
description: "Migrate address - `family-name` should be migrated to `name`",
record: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
"family-name": "Berners-Lee",
},
expectedResult: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
name: "Berners-Lee",
},
},
{
description:
"Migrate address - `name` should still be empty when there is no *-name fields in the record",
record: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
},
expectedResult: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
},
},
{
description:
"Migrate address - do not run migration as long as the name field exists",
record: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
// The cached field doesn't align "name" but it
// won't be recomputed because the migration isn't invoked.
"given-name": "Timothy",
"additional-name": "John",
"family-name": "Berners-Lee",
name: "Jane",
},
expectedResult: {
guid: "test-guid",
version: ADDRESS_SCHEMA_VERSION,
"given-name": "Timothy",
"additional-name": "John",
"family-name": "Berners-Lee",
name: "Jane",
},
},
]; ];
const CREDIT_CARD_TESTCASES = [ const CREDIT_CARD_TESTCASES = [

Просмотреть файл

@ -21,15 +21,15 @@ const ADDRESS_RECONCILE_TESTCASES = [
// So when we last wrote the record to the server, it had these values. // So when we last wrote the record to the server, it had these values.
guid: "2bbd2d8fbc6b", guid: "2bbd2d8fbc6b",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
local: [ local: [
{ {
// The current local record - by comparing against parent we can see that // The current local record - by comparing against parent we can see that
// only the given-name has changed locally. // only the name has changed locally.
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
], ],
remote: { remote: {
@ -38,13 +38,13 @@ const ADDRESS_RECONCILE_TESTCASES = [
// can safely ignore the incoming record and write our local changes. // can safely ignore the incoming record and write our local changes.
guid: "2bbd2d8fbc6b", guid: "2bbd2d8fbc6b",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
reconciled: { reconciled: {
guid: "2bbd2d8fbc6b", guid: "2bbd2d8fbc6b",
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -52,25 +52,25 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "e3680e9f890d", guid: "e3680e9f890d",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
local: [ local: [
{ {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
], ],
remote: { remote: {
guid: "e3680e9f890d", guid: "e3680e9f890d",
version: 1, version: 1,
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
reconciled: { reconciled: {
guid: "e3680e9f890d", guid: "e3680e9f890d",
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -78,26 +78,26 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "0cba738b1be0", guid: "0cba738b1be0",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
local: [ local: [
{ {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
}, },
], ],
remote: { remote: {
guid: "0cba738b1be0", guid: "0cba738b1be0",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
reconciled: { reconciled: {
guid: "0cba738b1be0", guid: "0cba738b1be0",
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
}, },
}, },
@ -106,26 +106,26 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "be3ef97f8285", guid: "be3ef97f8285",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
local: [ local: [
{ {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
], ],
remote: { remote: {
guid: "be3ef97f8285", guid: "be3ef97f8285",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
}, },
reconciled: { reconciled: {
guid: "be3ef97f8285", guid: "be3ef97f8285",
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
}, },
}, },
@ -134,27 +134,27 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "9627322248ec", guid: "9627322248ec",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
}, },
local: [ local: [
{ {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
], ],
remote: { remote: {
guid: "9627322248ec", guid: "9627322248ec",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
}, },
reconciled: { reconciled: {
guid: "9627322248ec", guid: "9627322248ec",
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -162,27 +162,27 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "7d7509f3eeb2", guid: "7d7509f3eeb2",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
}, },
local: [ local: [
{ {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
}, },
], ],
remote: { remote: {
guid: "7d7509f3eeb2", guid: "7d7509f3eeb2",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
reconciled: { reconciled: {
guid: "7d7509f3eeb2", guid: "7d7509f3eeb2",
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -191,17 +191,17 @@ const ADDRESS_RECONCILE_TESTCASES = [
// The last time we wrote this to the server, country was NZ. // The last time we wrote this to the server, country was NZ.
guid: "e087a06dfc57", guid: "e087a06dfc57",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "NZ", country: "NZ",
// We also had an unknown field we round-tripped // We also had an unknown field we round-tripped
foo: "bar", foo: "bar",
}, },
local: [ local: [
{ {
// The current local record - so locally we've changed given-name to Skip. // The current local record - so locally we've changed name to Skip.
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "NZ", country: "NZ",
}, },
], ],
@ -209,16 +209,16 @@ const ADDRESS_RECONCILE_TESTCASES = [
// Remotely, we've changed the country to AU. // Remotely, we've changed the country to AU.
guid: "e087a06dfc57", guid: "e087a06dfc57",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "AU", country: "AU",
// This is a new unknown field that should send instead! // This is a new unknown field that should send instead!
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
reconciled: { reconciled: {
guid: "e087a06dfc57", guid: "e087a06dfc57",
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "AU", country: "AU",
// This is a new unknown field that should send instead! // This is a new unknown field that should send instead!
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
@ -229,20 +229,20 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "340a078c596f", guid: "340a078c596f",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
country: "US", country: "US",
}, },
local: [ local: [
{ {
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "US", country: "US",
}, },
{ {
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
organization: "Mozilla", organization: "Mozilla",
country: "US", country: "US",
}, },
@ -250,15 +250,15 @@ const ADDRESS_RECONCILE_TESTCASES = [
remote: { remote: {
guid: "340a078c596f", guid: "340a078c596f",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
country: "AU", country: "AU",
}, },
reconciled: { reconciled: {
guid: "340a078c596f", guid: "340a078c596f",
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
organization: "Mozilla", organization: "Mozilla",
country: "AU", country: "AU",
}, },
@ -270,29 +270,29 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "0b3a72a1bea2", guid: "0b3a72a1bea2",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
// unknown fields we previously roundtripped // unknown fields we previously roundtripped
foo: "bar", foo: "bar",
}, },
local: [ local: [
{ {
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
], ],
remote: { remote: {
guid: "0b3a72a1bea2", guid: "0b3a72a1bea2",
version: 1, version: 1,
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
// New unknown field that should be the new round trip // New unknown field that should be the new round trip
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
reconciled: { reconciled: {
guid: "0b3a72a1bea2", guid: "0b3a72a1bea2",
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -301,37 +301,37 @@ const ADDRESS_RECONCILE_TESTCASES = [
// This is what we last wrote to the sync server. // This is what we last wrote to the sync server.
guid: "62068784d089", guid: "62068784d089",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
local: [ local: [
{ {
// The current version of the local record - the given-name has changed locally. // The current version of the local record - the name has changed locally.
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
], ],
remote: { remote: {
// An incoming record has a different given-name than any of the above! // An incoming record has a different name than any of the above!
guid: "62068784d089", guid: "62068784d089",
version: 1, version: 1,
"given-name": "Kip", name: "Kip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
forked: { forked: {
// So we've forked the local record to a new GUID (and the next sync is // So we've forked the local record to a new GUID (and the next sync is
// going to write this as a new record) // going to write this as a new record)
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
reconciled: { reconciled: {
// And we've updated the local version of the record to be the remote version. // And we've updated the local version of the record to be the remote version.
guid: "62068784d089", guid: "62068784d089",
"given-name": "Kip", name: "Kip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}, },
}, },
@ -340,33 +340,33 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "244dbb692e94", guid: "244dbb692e94",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "NZ", country: "NZ",
}, },
local: [ local: [
{ {
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "AU", country: "AU",
}, },
], ],
remote: { remote: {
guid: "244dbb692e94", guid: "244dbb692e94",
version: 1, version: 1,
"given-name": "Kip", name: "Kip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "CA", country: "CA",
}, },
forked: { forked: {
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "AU", country: "AU",
}, },
reconciled: { reconciled: {
guid: "244dbb692e94", guid: "244dbb692e94",
"given-name": "Kip", name: "Kip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "CA", country: "CA",
}, },
}, },
@ -375,31 +375,31 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "6fc45e03d19a", guid: "6fc45e03d19a",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "AU", country: "AU",
}, },
local: [ local: [
{ {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
], ],
remote: { remote: {
guid: "6fc45e03d19a", guid: "6fc45e03d19a",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "NZ", country: "NZ",
}, },
forked: { forked: {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
reconciled: { reconciled: {
guid: "6fc45e03d19a", guid: "6fc45e03d19a",
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "NZ", country: "NZ",
}, },
}, },
@ -408,32 +408,32 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "fff9fa27fa18", guid: "fff9fa27fa18",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "AU", country: "AU",
}, },
local: [ local: [
{ {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "NZ", country: "NZ",
}, },
], ],
remote: { remote: {
guid: "fff9fa27fa18", guid: "fff9fa27fa18",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
forked: { forked: {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
country: "NZ", country: "NZ",
}, },
reconciled: { reconciled: {
guid: "fff9fa27fa18", guid: "fff9fa27fa18",
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
}, },
{ {
@ -445,8 +445,8 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "5113f329c42f", guid: "5113f329c42f",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
timeCreated: 1234, timeCreated: 1234,
timeLastModified: 5678, timeLastModified: 5678,
timeLastUsed: 5678, timeLastUsed: 5678,
@ -456,8 +456,8 @@ const ADDRESS_RECONCILE_TESTCASES = [
remote: { remote: {
guid: "5113f329c42f", guid: "5113f329c42f",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
timeCreated: 1200, timeCreated: 1200,
timeLastModified: 5700, timeLastModified: 5700,
timeLastUsed: 5700, timeLastUsed: 5700,
@ -465,8 +465,8 @@ const ADDRESS_RECONCILE_TESTCASES = [
}, },
reconciled: { reconciled: {
guid: "5113f329c42f", guid: "5113f329c42f",
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
timeCreated: 1200, timeCreated: 1200,
timeLastModified: 5700, timeLastModified: 5700,
timeLastUsed: 5678, timeLastUsed: 5678,
@ -481,8 +481,8 @@ const ADDRESS_RECONCILE_TESTCASES = [
parent: { parent: {
guid: "791e5608b80a", guid: "791e5608b80a",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
timeCreated: 1234, timeCreated: 1234,
timeLastModified: 5678, timeLastModified: 5678,
timeLastUsed: 5678, timeLastUsed: 5678,
@ -490,15 +490,15 @@ const ADDRESS_RECONCILE_TESTCASES = [
}, },
local: [ local: [
{ {
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}, },
], ],
remote: { remote: {
guid: "791e5608b80a", guid: "791e5608b80a",
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
timeCreated: 1300, timeCreated: 1300,
timeLastModified: 5000, timeLastModified: 5000,
timeLastUsed: 5000, timeLastUsed: 5000,
@ -506,8 +506,8 @@ const ADDRESS_RECONCILE_TESTCASES = [
}, },
reconciled: { reconciled: {
guid: "791e5608b80a", guid: "791e5608b80a",
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
timeCreated: 1234, timeCreated: 1234,
timeLastUsed: 5678, timeLastUsed: 5678,
timesUsed: 6, timesUsed: 6,
@ -1022,8 +1022,8 @@ add_task(async function test_reconcile_unknown_version() {
profileStorage.addresses.reconcile({ profileStorage.addresses.reconcile({
guid: "31d83d2725ec", guid: "31d83d2725ec",
version: 3, version: 3,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
}), }),
/Got unknown record version/ /Got unknown record version/
); );
@ -1037,24 +1037,24 @@ add_task(async function test_reconcile_idempotent() {
{ {
guid, guid,
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
// an unknown field from a previous sync // an unknown field from a previous sync
foo: "bar", foo: "bar",
}, },
{ sourceSync: true } { sourceSync: true }
); );
await profileStorage.addresses.update(guid, { await profileStorage.addresses.update(guid, {
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
organization: "Mozilla", organization: "Mozilla",
}); });
let remote = { let remote = {
guid, guid,
version: 1, version: 1,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond", "street-address": "32 Vassar Street",
tel: "123456", tel: "123456",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
}; };
@ -1069,8 +1069,8 @@ add_task(async function test_reconcile_idempotent() {
ok( ok(
objectMatches(updatedRecord, { objectMatches(updatedRecord, {
guid: "de1ba7b094fe", guid: "de1ba7b094fe",
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
organization: "Mozilla", organization: "Mozilla",
tel: "123456", tel: "123456",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",
@ -1089,8 +1089,8 @@ add_task(async function test_reconcile_idempotent() {
ok( ok(
objectMatches(updatedRecord, { objectMatches(updatedRecord, {
guid: "de1ba7b094fe", guid: "de1ba7b094fe",
"given-name": "Skip", name: "Skip",
"family-name": "Hammond", "street-address": "32 Vassar Street",
organization: "Mozilla", organization: "Mozilla",
tel: "123456", tel: "123456",
"unknown-1": "an unknown field from another client", "unknown-1": "an unknown field from another client",

Просмотреть файл

@ -8,9 +8,7 @@
const TEST_STORE_FILE_NAME = "test-profile.json"; const TEST_STORE_FILE_NAME = "test-profile.json";
const TEST_ADDRESS_1 = { const TEST_ADDRESS_1 = {
"given-name": "Timothy", name: "Timothy John Berners-Lee",
"additional-name": "John",
"family-name": "Berners-Lee",
organization: "World Wide Web Consortium", organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\nMIT Room 32-G524", "street-address": "32 Vassar Street\nMIT Room 32-G524",
"address-level2": "Cambridge", "address-level2": "Cambridge",
@ -195,7 +193,7 @@ add_task(async function test_add_resurrects_tombstones() {
equal(guid, guid3); equal(guid, guid3);
let got = await profileStorage.addresses.get(guid); let got = await profileStorage.addresses.get(guid);
equal(got["given-name"], TEST_ADDRESS_1["given-name"]); equal(got.name, TEST_ADDRESS_1.name);
}); });
add_task(async function test_remove_sourceSync_localChanges() { add_task(async function test_remove_sourceSync_localChanges() {

Просмотреть файл

@ -25,9 +25,7 @@ initTestLogging("Trace");
const TEST_STORE_FILE_NAME = "test-profile.json"; const TEST_STORE_FILE_NAME = "test-profile.json";
const TEST_PROFILE_1 = { const TEST_PROFILE_1 = {
"given-name": "Timothy", name: "Timothy John Berners-Lee",
"additional-name": "John",
"family-name": "Berners-Lee",
organization: "World Wide Web Consortium", organization: "World Wide Web Consortium",
"street-address": "32 Vassar Street\nMIT Room 32-G524", "street-address": "32 Vassar Street\nMIT Room 32-G524",
"address-level2": "Cambridge", "address-level2": "Cambridge",
@ -272,7 +270,7 @@ add_task(async function test_incoming_existing() {
// now server records that modify the existing items. // now server records that modify the existing items.
let modifiedEntry1 = Object.assign({}, TEST_PROFILE_1, { let modifiedEntry1 = Object.assign({}, TEST_PROFILE_1, {
version: 1, version: 1,
"given-name": "NewName", name: "NewName",
}); });
let lastSync = await engine.getLastSync(); let lastSync = await engine.getLastSync();
@ -482,8 +480,7 @@ add_task(async function test_applyIncoming_incoming_restored() {
let localRecord = await profileStorage.addresses.get(guid); let localRecord = await profileStorage.addresses.get(guid);
ok( ok(
objectMatches(localRecord, { objectMatches(localRecord, {
"given-name": "Timothy", name: "Timothy John Berners-Lee",
"family-name": "Berners-Lee",
"street-address": "I moved!", "street-address": "I moved!",
}) })
); );
@ -537,8 +534,7 @@ add_task(async function test_applyIncoming_outgoing_restored() {
ok(!serverPayload.deleted, "Should resurrect record on server"); ok(!serverPayload.deleted, "Should resurrect record on server");
ok( ok(
objectMatches(serverPayload.entry, { objectMatches(serverPayload.entry, {
"given-name": "Timothy", name: "Timothy John Berners-Lee",
"family-name": "Berners-Lee",
"street-address": "I moved!", "street-address": "I moved!",
// resurrection also beings back any unknown fields we had // resurrection also beings back any unknown fields we had
"unknown-1": "some unknown data from another client", "unknown-1": "some unknown data from another client",
@ -750,8 +746,7 @@ add_task(async function test_dedupe_multiple_candidates() {
// overwrite that tombstone when we see A. // overwrite that tombstone when we see A.
let localRecord = { let localRecord = {
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond",
organization: "Mozilla", organization: "Mozilla",
country: "AU", country: "AU",
tel: "+12345678910", tel: "+12345678910",
@ -799,16 +794,14 @@ add_task(async function test_dedupe_multiple_candidates() {
await expectLocalProfiles(profileStorage, [ await expectLocalProfiles(profileStorage, [
{ {
guid: aGuid, guid: aGuid,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond",
organization: "Mozilla", organization: "Mozilla",
country: "AU", country: "AU",
tel: "+12345678910", tel: "+12345678910",
}, },
{ {
guid: bGuid, guid: bGuid,
"given-name": "Mark", name: "Mark Hammond",
"family-name": "Hammond",
organization: "Mozilla", organization: "Mozilla",
country: "AU", country: "AU",
tel: "+12345678910", tel: "+12345678910",
@ -875,14 +868,12 @@ add_task(async function test_reconcile_both_modified_conflict() {
await expectLocalProfiles(profileStorage, [ await expectLocalProfiles(profileStorage, [
{ {
guid, guid,
"given-name": "Timothy", name: "Timothy John Berners-Lee",
"family-name": "Berners-Lee",
"street-address": "I moved, too!", "street-address": "I moved, too!",
}, },
{ {
guid: forkedPayload.id, guid: forkedPayload.id,
"given-name": "Timothy", name: "Timothy John Berners-Lee",
"family-name": "Berners-Lee",
"street-address": "I moved!", "street-address": "I moved!",
}, },
]); ]);

Разница между файлами не показана из-за своего большого размера Загрузить разницу

Просмотреть файл

@ -203,23 +203,18 @@ const ADDRESS_NORMALIZE_TESTCASES = [
name: "Timothy John Berners-Lee", name: "Timothy John Berners-Lee",
}, },
expectedResult: { expectedResult: {
"given-name": "Timothy", name: "Timothy John Berners-Lee",
"additional-name": "John",
"family-name": "Berners-Lee",
}, },
}, },
{ {
description: 'Has both "name" and split names', description: 'Has both "name" and split names',
address: { address: {
name: "John Doe", name: "Timothy John Berners-Lee",
"given-name": "Timothy", "given-name": "John",
"additional-name": "John", "family-name": "Doe",
"family-name": "Berners-Lee",
}, },
expectedResult: { expectedResult: {
"given-name": "Timothy", name: "Timothy John Berners-Lee",
"additional-name": "John",
"family-name": "Berners-Lee",
}, },
}, },
{ {
@ -229,9 +224,39 @@ const ADDRESS_NORMALIZE_TESTCASES = [
"given-name": "Timothy", "given-name": "Timothy",
}, },
expectedResult: { expectedResult: {
name: "John Doe",
},
},
{
description: 'Does not have "name", and has split names',
address: {
"given-name": "Timothy", "given-name": "Timothy",
"additional-name": "John",
"family-name": "Berners-Lee",
},
expectedResult: {
name: "Timothy John Berners-Lee",
},
},
{
description: 'Does not have "name", and has some split names',
address: {
"given-name": "John",
"family-name": "Doe", "family-name": "Doe",
}, },
expectedResult: {
name: "John Doe",
},
},
{
description: 'Does not have "name" and split names',
address: {
organization: "Mozilla",
},
expectedResult: {
name: undefined,
organization: "Mozilla",
},
}, },
// Address // Address

Просмотреть файл

@ -40,13 +40,8 @@ head = "head_addressComponent.js"
["test_addressDataLoader.js"] ["test_addressDataLoader.js"]
["test_addressRecords.js"] ["test_addressRecords.js"]
skip-if = ["apple_silicon"] # bug 1729554
["test_autofillFormFields.js"] ["test_autofillFormFields.js"]
skip-if = [
"tsan", # Times out, bug 1612707
"apple_silicon", # bug 1729554
]
["test_clearPopulatedForm.js"] ["test_clearPopulatedForm.js"]
@ -55,10 +50,6 @@ skip-if = [
["test_createRecords.js"] ["test_createRecords.js"]
["test_creditCardRecords.js"] ["test_creditCardRecords.js"]
skip-if = [
"tsan", # Times out, bug 1612707
"apple_silicon", # bug 1729554
]
["test_extractLabelStrings.js"] ["test_extractLabelStrings.js"]
@ -77,10 +68,6 @@ skip-if = [
["test_getInfo.js"] ["test_getInfo.js"]
["test_getRecords.js"] ["test_getRecords.js"]
skip-if = [
"tsan", # Times out, bug 1612707
"apple_silicon", # bug 1729554
]
["test_isAddressAutofillAvailable.js"] ["test_isAddressAutofillAvailable.js"]
@ -113,26 +100,14 @@ skip-if = ["tsan"] # Times out, bug 1612707
["test_profileAutocompleteResult.js"] ["test_profileAutocompleteResult.js"]
["test_reconcile.js"] ["test_reconcile.js"]
skip-if = [
"tsan", # Times out, bug 1612707
"apple_silicon", # bug 1729554
]
["test_savedFieldNames.js"] ["test_savedFieldNames.js"]
["test_storage_remove.js"] ["test_storage_remove.js"]
skip-if = [
"tsan", # Times out, bug 1612707
"apple_silicon", # bug 1729554
]
["test_storage_syncfields.js"] ["test_storage_syncfields.js"]
["test_storage_tombstones.js"] ["test_storage_tombstones.js"]
skip-if = [
"tsan", # Times out, bug 1612707
"apple_silicon", # bug 1729554
]
["test_sync.js"] ["test_sync.js"]
head = "head.js ../../../../../services/sync/tests/unit/head_appinfo.js ../../../../../services/common/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_http_server.js" head = "head.js ../../../../../services/sync/tests/unit/head_appinfo.js ../../../../../services/common/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_http_server.js"
@ -140,15 +115,10 @@ skip-if = ["tsan"] # Times out, bug 1612707
["test_sync_deprecate_credit_card_v4.js"] ["test_sync_deprecate_credit_card_v4.js"]
head = "head.js ../../../../../services/sync/tests/unit/head_appinfo.js ../../../../../services/common/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_http_server.js" head = "head.js ../../../../../services/sync/tests/unit/head_appinfo.js ../../../../../services/common/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_http_server.js"
skip-if = [
"tsan", # Times out, bug 1612707 ["test_sync_deprecate_address_x_name_fields.js"]
"apple_silicon", # bug 1729554 head = "head.js ../../../../../services/sync/tests/unit/head_appinfo.js ../../../../../services/common/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_helpers.js ../../../../../services/sync/tests/unit/head_http_server.js"
]
["test_toOneLineAddress.js"] ["test_toOneLineAddress.js"]
["test_transformFields.js"] ["test_transformFields.js"]
skip-if = [
"tsan", # Times out, bug 1612707
"apple_silicon", # bug 1729554
]

Просмотреть файл

@ -20,6 +20,7 @@
* given-name, * given-name,
* additional-name, * additional-name,
* family-name, * family-name,
* name,
* organization, // Company * organization, // Company
* street-address, // (Multiline) * street-address, // (Multiline)
* address-level3, // Suburb/Sublocality * address-level3, // Suburb/Sublocality
@ -32,7 +33,9 @@
* *
* // computed fields (These fields are computed based on the above fields * // computed fields (These fields are computed based on the above fields
* // and are not allowed to be modified directly.) * // and are not allowed to be modified directly.)
* name, * given-name,
* additional-name,
* family-name,
* address-line1, * address-line1,
* address-line2, * address-line2,
* address-line3, * address-line3,
@ -163,20 +166,7 @@ export const ADDRESS_SCHEMA_VERSION = 1;
// Please talk to the sync team before changing this! // Please talk to the sync team before changing this!
export const CREDIT_CARD_SCHEMA_VERSION = 3; export const CREDIT_CARD_SCHEMA_VERSION = 3;
const VALID_ADDRESS_FIELDS = [ const NAME_COMPONENTS = ["given-name", "additional-name", "family-name"];
"given-name",
"additional-name",
"family-name",
"organization",
"street-address",
"address-level3",
"address-level2",
"address-level1",
"postal-code",
"country",
"tel",
"email",
];
const STREET_ADDRESS_COMPONENTS = [ const STREET_ADDRESS_COMPONENTS = [
"address-line1", "address-line1",
@ -193,10 +183,25 @@ const TEL_COMPONENTS = [
"tel-local-suffix", "tel-local-suffix",
]; ];
const VALID_ADDRESS_COMPUTED_FIELDS = ["name", "country-name"].concat( const VALID_ADDRESS_FIELDS = [
STREET_ADDRESS_COMPONENTS, "name",
TEL_COMPONENTS "organization",
); "street-address",
"address-level3",
"address-level2",
"address-level1",
"postal-code",
"country",
"tel",
"email",
];
const VALID_ADDRESS_COMPUTED_FIELDS = [
"country-name",
...NAME_COMPONENTS,
...STREET_ADDRESS_COMPONENTS,
...TEL_COMPONENTS,
];
const VALID_CREDIT_CARD_FIELDS = [ const VALID_CREDIT_CARD_FIELDS = [
"billingAddressGUID", "billingAddressGUID",
@ -665,7 +670,13 @@ class AutofillRecords {
// The record is cloned to avoid accidental modifications from outside. // The record is cloned to avoid accidental modifications from outside.
let clonedRecord = this._cloneAndCleanUp(recordFound); let clonedRecord = this._cloneAndCleanUp(recordFound);
if (rawData) { if (rawData) {
await this._stripComputedFields(clonedRecord); // The *-name fields, previously listed in VALID_FIELDS, have been moved to
// COMPUTED_FIELDS. By default, the sync payload includes only those fields in VALID_FIELDS.
// Excluding *-name fields from the sync payload would prevent older devices from
// synchronizing with newer devices. To maintain backward compatibility, keep those deprecated
// ields in the payload, ensuring that older devices can still sync with newer devices.
const fieldsToKeep = NAME_COMPONENTS;
await this._stripComputedFields(clonedRecord, fieldsToKeep);
} else { } else {
this._recordReadProcessor(clonedRecord); this._recordReadProcessor(clonedRecord);
} }
@ -692,7 +703,8 @@ class AutofillRecords {
await Promise.all( await Promise.all(
clonedRecords.map(async record => { clonedRecords.map(async record => {
if (rawData) { if (rawData) {
await this._stripComputedFields(record); const fieldsToKeep = NAME_COMPONENTS;
await this._stripComputedFields(record, fieldsToKeep);
} else { } else {
this._recordReadProcessor(record); this._recordReadProcessor(record);
} }
@ -1367,7 +1379,7 @@ class AutofillRecords {
record.version = 0; record.version = 0;
} }
if (this._isMigrationNeeded(record.version)) { if (this._isMigrationNeeded(record)) {
hasChanges = true; hasChanges = true;
record = await this._computeMigratedRecord(record); record = await this._computeMigratedRecord(record);
@ -1441,8 +1453,8 @@ class AutofillRecords {
); );
} }
_isMigrationNeeded(recordVersion) { _isMigrationNeeded(record) {
return recordVersion < this.version; return record.version < this.version;
} }
/** /**
@ -1464,8 +1476,13 @@ class AutofillRecords {
return record; return record;
} }
async _stripComputedFields(record) { async _stripComputedFields(record, fieldsToKeep = []) {
this.VALID_COMPUTED_FIELDS.forEach(field => delete record[field]); for (const field of this.VALID_COMPUTED_FIELDS) {
if (fieldsToKeep.includes(field)) {
continue;
}
delete record[field];
}
} }
// An interface to be inherited. // An interface to be inherited.
@ -1496,6 +1513,9 @@ class AutofillRecords {
* @throws * @throws
*/ */
_validateFields(record) {} _validateFields(record) {}
// An interface to be inherited.
migrateRemoteRecord(remoteRecord) {}
} }
export class AddressesBase extends AutofillRecords { export class AddressesBase extends AutofillRecords {
@ -1516,6 +1536,40 @@ export class AddressesBase extends AutofillRecords {
} }
} }
_isMigrationNeeded(record) {
if (super._isMigrationNeeded(record)) {
return true;
}
if (
!record.name &&
(record["given-name"] ||
record["additional-name"] ||
record["family-name"])
) {
return true;
}
return false;
}
async _computeMigratedRecord(address) {
// Bug 1836438 - `name` field was moved from computed fields to valid fields.
if (
!address.name &&
(address["given-name"] ||
address["additional-name"] ||
address["family-name"])
) {
address.name = lazy.FormAutofillNameUtils.joinNameParts({
given: address["given-name"] ?? "",
middle: address["additional-name"] ?? "",
family: address["family-name"] ?? "",
});
}
return super._computeMigratedRecord(address);
}
async computeFields(address) { async computeFields(address) {
// NOTE: Remember to bump the schema version number if any of the existing // NOTE: Remember to bump the schema version number if any of the existing
// computing algorithm changes. (No need to bump when just adding new // computing algorithm changes. (No need to bump when just adding new
@ -1530,14 +1584,12 @@ export class AddressesBase extends AutofillRecords {
return hasNewComputedFields; return hasNewComputedFields;
} }
// Compute name // Compute split names
if (!("name" in address)) { if (!("given-name" in address)) {
let name = lazy.FormAutofillNameUtils.joinNameParts({ const nameParts = lazy.FormAutofillNameUtils.splitName(address.name);
given: address["given-name"], address["given-name"] = nameParts.given;
middle: address["additional-name"], address["additional-name"] = nameParts.middle;
family: address["family-name"], address["family-name"] = nameParts.family;
});
address.name = name;
hasNewComputedFields = true; hasNewComputedFields = true;
} }
@ -1629,19 +1681,22 @@ export class AddressesBase extends AutofillRecords {
} }
_normalizeNameFields(address) { _normalizeNameFields(address) {
if (address.name) { if (
let nameParts = lazy.FormAutofillNameUtils.splitName(address.name); !address.name &&
if (!address["given-name"] && nameParts.given) { (address["given-name"] ||
address["given-name"] = nameParts.given; address["additional-name"] ||
} address["family-name"])
if (!address["additional-name"] && nameParts.middle) { ) {
address["additional-name"] = nameParts.middle; address.name = lazy.FormAutofillNameUtils.joinNameParts({
} given: address["given-name"] ?? "",
if (!address["family-name"] && nameParts.family) { middle: address["additional-name"] ?? "",
address["family-name"] = nameParts.family; family: address["family-name"] ?? "",
} });
} }
delete address.name;
delete address["given-name"];
delete address["additional-name"];
delete address["family-name"];
} }
_normalizeAddressFields(address) { _normalizeAddressFields(address) {
@ -1711,6 +1766,57 @@ export class AddressesBase extends AutofillRecords {
} }
TEL_COMPONENTS.forEach(c => delete address[c]); TEL_COMPONENTS.forEach(c => delete address[c]);
} }
/**
* Migrate the remote record to the expected format.
*
* @param {object} remoteRecord The remote record.
*/
migrateRemoteRecord(remoteRecord) {
// When a remote record lacks the `name` field but includes any `*-name` fields, we can
// assume that the record originates from an older device. This is because even if an older
// device pulls the `name` field from a newer record from the sync server, the `name` field,
// being a computed field for an older device, will always be stripped.
// If the remote record comes from an older device, we compare the `*-name` fields in the
// remote record with those in the corresponding local record. If the values of the `*-name`
// fields differ, it indicates that the remote record has updated these fields. If the
// values are the same, we replace the name field of the remote record with the local
// name field to ensure the completeness of the name field when reconciling.
//
// Here is an example:
// Assume the local record is {"name": "Mr. John Doe"}. If an updated remote record
// has {"given-name": "John", "family-name": "Doe"}, we will NOT join the `*-name` fields
// and replace the local `name` field with "John Doe". This allows us to retain the complete
// name - "Mr. John Doe".
// However, if the updated remote record has {"given-name": "Jane", "family-name": "Poe"},
// we will rebuild it and replace the local `name` field with "Jane Poe".
if (
!("name" in remoteRecord) &&
NAME_COMPONENTS.some(c => c in remoteRecord)
) {
const localRecord = this._findByGUID(remoteRecord.guid);
if (
localRecord &&
NAME_COMPONENTS.every(c => remoteRecord[c] == localRecord[c])
) {
remoteRecord.name = localRecord.name;
} else {
remoteRecord.name = lazy.FormAutofillNameUtils.joinNameParts({
given: remoteRecord["given-name"],
middle: remoteRecord["additional-name"],
family: remoteRecord["family-name"],
});
}
}
// To enable new devices to sync name field changes with older devices, we still
// include the computed *-name fields in the sync payload while uploading.
// This also means that the incoming remote record will also contain *-name fields.
// However, since the autofill storage does not expect remote records to contain
// computed fields while merging, we remove them from the remote record.
NAME_COMPONENTS.forEach(f => delete remoteRecord[f]);
}
} }
export class CreditCardsBase extends AutofillRecords { export class CreditCardsBase extends AutofillRecords {
@ -1745,7 +1851,7 @@ export class CreditCardsBase extends AutofillRecords {
// Compute split names // Compute split names
if (!("cc-given-name" in creditCard)) { if (!("cc-given-name" in creditCard)) {
let nameParts = lazy.FormAutofillNameUtils.splitName( const nameParts = lazy.FormAutofillNameUtils.splitName(
creditCard["cc-name"] creditCard["cc-name"]
); );
creditCard["cc-given-name"] = nameParts.given; creditCard["cc-given-name"] = nameParts.given;
@ -1777,10 +1883,10 @@ export class CreditCardsBase extends AutofillRecords {
throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED); throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);
} }
_isMigrationNeeded(recordVersion) { _isMigrationNeeded(record) {
return ( return (
// version 4 is deprecated and is rolled back to version 3 // version 4 is deprecated and is rolled back to version 3
recordVersion == 4 || recordVersion < this.version record.version == 4 || record.version < this.version
); );
} }

Просмотреть файл

@ -114,6 +114,19 @@ FormAutofillStore.prototype = {
return; return;
} }
// Records from the remote might come from an older device. To ensure that
// remote records from older devices can still sync with the local records,
// we migrate the remote records. This enables the merging of older records
// with newer records.
//
// Currently, this migration is only used for converting `*-name` fields to `name` fields.
// The migration process involves:
// 1. Generating a `name` field so we don't assume the `name` field is empty, thereby
// avoiding erasing its value.
// 2. Removing deprecated *-name fields from the remote record because the autofill storage
// does not expect to see those fields.
this.storage.migrateRemoteRecord(remoteRecord.entry);
if (await this.itemExists(remoteRecord.id)) { if (await this.itemExists(remoteRecord.id)) {
// We will never get a tombstone here, so we are updating a real record. // We will never get a tombstone here, so we are updating a real record.
await this._doUpdateRecord(remoteRecord); await this._doUpdateRecord(remoteRecord);

Просмотреть файл

@ -18,8 +18,6 @@ const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, { ChromeUtils.defineESModuleGetters(lazy, {
CreditCard: "resource://gre/modules/CreditCard.sys.mjs", CreditCard: "resource://gre/modules/CreditCard.sys.mjs",
FormAutofillNameUtils:
"resource://gre/modules/shared/FormAutofillNameUtils.sys.mjs",
formAutofillStorage: "resource://autofill/FormAutofillStorage.sys.mjs", formAutofillStorage: "resource://autofill/FormAutofillStorage.sys.mjs",
}); });
@ -463,13 +461,6 @@ export class AddressSaveDoorhanger extends AutofillDoorhanger {
#formatTextByAddressCategory(fieldName) { #formatTextByAddressCategory(fieldName) {
let data = []; let data = [];
switch (fieldName) { switch (fieldName) {
case "name":
data = ["given-name", "additional-name", "family-name"].map(field => [
field,
this.oldRecord[field],
this.newRecord[field],
]);
break;
case "street-address": case "street-address":
data = [ data = [
[ [
@ -488,6 +479,7 @@ export class AddressSaveDoorhanger extends AutofillDoorhanger {
field => [field, this.oldRecord[field], this.newRecord[field]] field => [field, this.oldRecord[field], this.newRecord[field]]
); );
break; break;
case "name":
case "country": case "country":
case "tel": case "tel":
case "email": case "email":
@ -684,17 +676,6 @@ export class AddressEditDoorhanger extends AutofillDoorhanger {
); );
} }
#getFieldDisplayData(field) {
if (field == "name") {
return lazy.FormAutofillNameUtils.joinNameParts({
given: this.newRecord["given-name"],
middle: this.newRecord["additional-name"],
family: this.newRecord["family-name"],
});
}
return this.newRecord[field];
}
#buildCountryMenupopup() { #buildCountryMenupopup() {
const menupopup = this.doc.createXULElement("menupopup"); const menupopup = this.doc.createXULElement("menupopup");
@ -806,7 +787,7 @@ export class AddressEditDoorhanger extends AutofillDoorhanger {
input.setAttribute("id", inputId); input.setAttribute("id", inputId);
const value = this.#getFieldDisplayData(fieldName) ?? null; const value = this.newRecord[fieldName] ?? "";
if (popup) { if (popup) {
const menuitem = Array.from(popup.childNodes).find( const menuitem = Array.from(popup.childNodes).find(
item => item =>

Просмотреть файл

@ -645,17 +645,7 @@ class Name extends AddressField {
} }
static fromRecord(record, region) { static fromRecord(record, region) {
let name; return new Name(record[Name.ac], region);
if (record.name) {
name = record.name;
} else {
name = lazy.FormAutofillNameUtils.joinNameParts({
given: record["given-name"],
middle: record["additional-name"],
family: record["family-name"],
});
}
return new Name(name, region);
} }
} }
@ -1025,37 +1015,35 @@ export class AddressComponent {
this.record = {}; this.record = {};
// Get country code first so we can use it to parse other fields // Get country code first so we can use it to parse other fields
const country = Country.fromRecord(record, FormAutofill.DEFAULT_REGION); const country = new Country(
record[Country.ac],
FormAutofill.DEFAULT_REGION
);
const region = const region =
country.country_code || country.country_code ||
lazy.FormAutofillUtils.identifyCountryCode(FormAutofill.DEFAULT_REGION); lazy.FormAutofillUtils.identifyCountryCode(FormAutofill.DEFAULT_REGION);
let fields = [ // Build an mapping that the key is field name and the value is the AddressField object
[
country, country,
StreetAddress.fromRecord(record, region), new StreetAddress(record[StreetAddress.ac], region),
PostalCode.fromRecord(record, region), new PostalCode(record[PostalCode.ac], region),
State.fromRecord(record, region), new State(record[State.ac], region),
City.fromRecord(record, region), new City(record[City.ac], region),
Name.fromRecord(record, region), new Name(record[Name.ac], region),
Tel.fromRecord(record, region), new Tel(record[Tel.ac], region),
Organization.fromRecord(record, region), new Organization(record[Organization.ac], region),
Email.fromRecord(record, region), new Email(record[Email.ac], region),
]; ].forEach(addressField => {
if (
for (const field of fields) { !addressField.isEmpty() &&
if (field.isEmpty() || (ignoreInvalid && !field.isValid())) { (!ignoreInvalid || addressField.isValid())
continue; ) {
const fieldName = addressField.constructor.ac;
this.#fields[fieldName] = addressField;
this.record[fieldName] = record[fieldName];
} }
this.#fields[field.constructor.ac] = field; });
if (field.constructor.ac == "name") {
this.record["given-name"] = record["given-name"] ?? "";
this.record["additional-name"] = record["additional-name"] ?? "";
this.record["family-name"] = record["family-name"] ?? "";
} else {
this.record[field.constructor.ac] = record[field.constructor.ac];
}
}
} }
/** /**