| Index: components/password_manager/core/browser/password_syncable_service.cc
|
| diff --git a/components/password_manager/core/browser/password_syncable_service.cc b/components/password_manager/core/browser/password_syncable_service.cc
|
| index 75d20b2aeb5c91aacc9f1317575af85918021162..6584b88d194131ed25df970e8cd1b3a0065c1edd 100644
|
| --- a/components/password_manager/core/browser/password_syncable_service.cc
|
| +++ b/components/password_manager/core/browser/password_syncable_service.cc
|
| @@ -12,8 +12,12 @@
|
| #include "base/location.h"
|
| #include "base/memory/ptr_util.h"
|
| #include "base/metrics/histogram_macros.h"
|
| +#include "base/optional.h"
|
| +#include "base/strings/string_util.h"
|
| #include "base/strings/utf_string_conversions.h"
|
| +#include "base/time/default_clock.h"
|
| #include "components/autofill/core/common/password_form.h"
|
| +#include "components/password_manager/core/browser/android_affiliation/affiliation_utils.h"
|
| #include "components/password_manager/core/browser/password_manager_metrics_util.h"
|
| #include "components/password_manager/core/browser/password_store_sync.h"
|
| #include "components/sync/model/sync_error_factory.h"
|
| @@ -36,20 +40,12 @@ std::string MakePasswordSyncTag(const autofill::PasswordForm& password);
|
| namespace {
|
|
|
| // Returns true iff |password_specifics| and |password_specifics| are equal
|
| -// memberwise.
|
| -bool AreLocalAndSyncPasswordsEqual(
|
| +// in the fields which don't comprise the sync tag.
|
| +bool AreLocalAndSyncPasswordsNonSyncTagEqual(
|
| const sync_pb::PasswordSpecificsData& password_specifics,
|
| const autofill::PasswordForm& password_form) {
|
| return (password_form.scheme == password_specifics.scheme() &&
|
| - password_form.signon_realm == password_specifics.signon_realm() &&
|
| - password_form.origin.spec() == password_specifics.origin() &&
|
| password_form.action.spec() == password_specifics.action() &&
|
| - base::UTF16ToUTF8(password_form.username_element) ==
|
| - password_specifics.username_element() &&
|
| - base::UTF16ToUTF8(password_form.password_element) ==
|
| - password_specifics.password_element() &&
|
| - base::UTF16ToUTF8(password_form.username_value) ==
|
| - password_specifics.username_value() &&
|
| base::UTF16ToUTF8(password_form.password_value) ==
|
| password_specifics.password_value() &&
|
| password_form.preferred == password_specifics.preferred() &&
|
| @@ -66,6 +62,38 @@ bool AreLocalAndSyncPasswordsEqual(
|
| password_form.federation_origin.Serialize());
|
| }
|
|
|
| +// Returns true iff |password_specifics| and |password_specifics| are equal
|
| +// memberwise.
|
| +bool AreLocalAndSyncPasswordsEqual(
|
| + const sync_pb::PasswordSpecificsData& password_specifics,
|
| + const autofill::PasswordForm& password_form) {
|
| + return (password_form.signon_realm == password_specifics.signon_realm() &&
|
| + password_form.origin.spec() == password_specifics.origin() &&
|
| + base::UTF16ToUTF8(password_form.username_element) ==
|
| + password_specifics.username_element() &&
|
| + base::UTF16ToUTF8(password_form.password_element) ==
|
| + password_specifics.password_element() &&
|
| + base::UTF16ToUTF8(password_form.username_value) ==
|
| + password_specifics.username_value() &&
|
| + AreLocalAndSyncPasswordsNonSyncTagEqual(password_specifics,
|
| + password_form));
|
| +}
|
| +
|
| +// Compares the fields which are not part of the sync tag.
|
| +bool AreNonSyncTagFieldsEqual(const sync_pb::PasswordSpecificsData& left,
|
| + const sync_pb::PasswordSpecificsData& right) {
|
| + return (left.scheme() == right.scheme() && left.action() == right.action() &&
|
| + left.password_value() == right.password_value() &&
|
| + left.preferred() == right.preferred() &&
|
| + left.date_created() == right.date_created() &&
|
| + left.blacklisted() == right.blacklisted() &&
|
| + left.type() == right.type() &&
|
| + left.times_used() == right.times_used() &&
|
| + left.display_name() == right.display_name() &&
|
| + left.avatar_url() == right.avatar_url() &&
|
| + left.federation_url() == right.federation_url());
|
| +}
|
| +
|
| syncer::SyncChange::SyncChangeType GetSyncChangeType(
|
| PasswordStoreChange::Type type) {
|
| switch (type) {
|
| @@ -91,6 +119,190 @@ void AppendPasswordFromSpecifics(
|
| entries->back()->date_synced = sync_time;
|
| }
|
|
|
| +// Android autofill saves credential in a different format without trailing '/'.
|
| +std::string GetIncorrectAndroidSignonRealm(std::string android_autofill_realm) {
|
| + if (base::EndsWith(android_autofill_realm, "/", base::CompareCase::SENSITIVE))
|
| + android_autofill_realm.erase(android_autofill_realm.size() - 1);
|
| + return android_autofill_realm;
|
| +}
|
| +
|
| +// The correct Android signon_realm should have a trailing '/'.
|
| +std::string GetCorrectAndroidSignonRealm(std::string android_realm) {
|
| + if (!base::EndsWith(android_realm, "/", base::CompareCase::SENSITIVE))
|
| + android_realm += '/';
|
| + return android_realm;
|
| +}
|
| +
|
| +// Android autofill saves credentials in a different format without trailing
|
| +// '/'. Return a sync tag for the style used by Android Autofill in GMS Core
|
| +// v12.
|
| +std::string AndroidAutofillSyncTag(
|
| + const sync_pb::PasswordSpecificsData& password) {
|
| + // realm has the same value as the origin.
|
| + std::string origin = GetIncorrectAndroidSignonRealm(password.origin());
|
| + std::string signon_realm =
|
| + GetIncorrectAndroidSignonRealm(password.signon_realm());
|
| + return (net::EscapePath(origin) + "|" +
|
| + net::EscapePath(password.username_element()) + "|" +
|
| + net::EscapePath(password.username_value()) + "|" +
|
| + net::EscapePath(password.password_element()) + "|" +
|
| + net::EscapePath(signon_realm));
|
| +}
|
| +
|
| +// Return a sync tag for the correct format used by Android.
|
| +std::string AndroidCorrectSyncTag(
|
| + const sync_pb::PasswordSpecificsData& password) {
|
| + // realm has the same value as the origin.
|
| + std::string origin = GetCorrectAndroidSignonRealm(password.origin());
|
| + std::string signon_realm =
|
| + GetCorrectAndroidSignonRealm(password.signon_realm());
|
| + return (net::EscapePath(origin) + "|" +
|
| + net::EscapePath(password.username_element()) + "|" +
|
| + net::EscapePath(password.username_value()) + "|" +
|
| + net::EscapePath(password.password_element()) + "|" +
|
| + net::EscapePath(signon_realm));
|
| +}
|
| +
|
| +void PasswordSpecificsFromPassword(
|
| + const autofill::PasswordForm& password_form,
|
| + sync_pb::PasswordSpecificsData* password_specifics) {
|
| +#define CopyField(field) password_specifics->set_##field(password_form.field)
|
| +#define CopyStringField(field) \
|
| + password_specifics->set_##field(base::UTF16ToUTF8(password_form.field))
|
| + CopyField(scheme);
|
| + CopyField(signon_realm);
|
| + password_specifics->set_origin(password_form.origin.spec());
|
| + password_specifics->set_action(password_form.action.spec());
|
| + CopyStringField(username_element);
|
| + CopyStringField(password_element);
|
| + CopyStringField(username_value);
|
| + CopyStringField(password_value);
|
| + CopyField(preferred);
|
| + password_specifics->set_date_created(
|
| + password_form.date_created.ToInternalValue());
|
| + password_specifics->set_blacklisted(password_form.blacklisted_by_user);
|
| + CopyField(type);
|
| + CopyField(times_used);
|
| + CopyStringField(display_name);
|
| + password_specifics->set_avatar_url(password_form.icon_url.spec());
|
| + password_specifics->set_federation_url(
|
| + password_form.federation_origin.unique()
|
| + ? std::string()
|
| + : password_form.federation_origin.Serialize());
|
| +#undef CopyStringField
|
| +#undef CopyField
|
| +}
|
| +
|
| +struct AndroidMergeResult {
|
| + // New value for Android entry in the correct format.
|
| + base::Optional<syncer::SyncData> new_android_correct;
|
| + // New value for Android autofill entry.
|
| + base::Optional<syncer::SyncData> new_android_incorrect;
|
| + // New value for local entry in the correct format.
|
| + base::Optional<autofill::PasswordForm> new_local_correct;
|
| + // New value for local entry in the Android autofill format.
|
| + base::Optional<autofill::PasswordForm> new_local_incorrect;
|
| +};
|
| +
|
| +// Perform deduplication of Android credentials saved in the wrong format. As
|
| +// the result all the four entries should be created and have the same value.
|
| +AndroidMergeResult Perform4WayMergeAndroidCredentials(
|
| + const sync_pb::PasswordSpecificsData* correct_android,
|
| + const sync_pb::PasswordSpecificsData* incorrect_android,
|
| + const autofill::PasswordForm* correct_local,
|
| + const autofill::PasswordForm* incorrect_local) {
|
| + AndroidMergeResult result;
|
| +
|
| + base::Optional<sync_pb::PasswordSpecificsData> local_correct_ps;
|
| + if (correct_local) {
|
| + local_correct_ps = sync_pb::PasswordSpecificsData();
|
| + PasswordSpecificsFromPassword(*correct_local, &local_correct_ps.value());
|
| + }
|
| +
|
| + base::Optional<sync_pb::PasswordSpecificsData> local_incorrect_ps;
|
| + if (incorrect_local) {
|
| + local_incorrect_ps = sync_pb::PasswordSpecificsData();
|
| + PasswordSpecificsFromPassword(*incorrect_local,
|
| + &local_incorrect_ps.value());
|
| + }
|
| +
|
| + const sync_pb::PasswordSpecificsData* all_data[4] = {
|
| + correct_android, incorrect_android,
|
| + local_correct_ps ? &local_correct_ps.value() : nullptr,
|
| + local_incorrect_ps ? &local_incorrect_ps.value() : nullptr};
|
| +
|
| + // |newest_data| will point to the newest entry out of all 4.
|
| + const sync_pb::PasswordSpecificsData* newest_data = nullptr;
|
| + for (int i = 0; i < 4; ++i) {
|
| + if (newest_data && all_data[i]) {
|
| + if (all_data[i]->date_created() > newest_data->date_created())
|
| + newest_data = all_data[i];
|
| + } else if (all_data[i]) {
|
| + newest_data = all_data[i];
|
| + }
|
| + }
|
| + DCHECK(newest_data);
|
| +
|
| + const std::string correct_tag = AndroidCorrectSyncTag(*newest_data);
|
| + const std::string incorrect_tag = AndroidAutofillSyncTag(*newest_data);
|
| + const std::string correct_signon_realm =
|
| + GetCorrectAndroidSignonRealm(newest_data->signon_realm());
|
| + const std::string incorrect_signon_realm =
|
| + GetIncorrectAndroidSignonRealm(newest_data->signon_realm());
|
| + const std::string correct_origin =
|
| + GetCorrectAndroidSignonRealm(newest_data->origin());
|
| + const std::string incorrect_origin =
|
| + GetIncorrectAndroidSignonRealm(newest_data->origin());
|
| + DCHECK_EQ(GURL(incorrect_origin).spec(), incorrect_origin);
|
| +
|
| + // Set the correct Sync entry if needed.
|
| + if (newest_data != correct_android &&
|
| + (!correct_android ||
|
| + !AreNonSyncTagFieldsEqual(*correct_android, *newest_data))) {
|
| + sync_pb::EntitySpecifics password_data;
|
| + sync_pb::PasswordSpecificsData* password_specifics =
|
| + password_data.mutable_password()->mutable_client_only_encrypted_data();
|
| + *password_specifics = *newest_data;
|
| + password_specifics->set_origin(correct_origin);
|
| + password_specifics->set_signon_realm(correct_signon_realm);
|
| + result.new_android_correct = syncer::SyncData::CreateLocalData(
|
| + correct_tag, correct_tag, password_data);
|
| + }
|
| +
|
| + // Set the Andoroid Autofill Sync entry if needed.
|
| + if (newest_data != incorrect_android &&
|
| + (!incorrect_android ||
|
| + !AreNonSyncTagFieldsEqual(*incorrect_android, *newest_data))) {
|
| + sync_pb::EntitySpecifics password_data;
|
| + sync_pb::PasswordSpecificsData* password_specifics =
|
| + password_data.mutable_password()->mutable_client_only_encrypted_data();
|
| + *password_specifics = *newest_data;
|
| + password_specifics->set_origin(incorrect_origin);
|
| + password_specifics->set_signon_realm(incorrect_signon_realm);
|
| + result.new_android_incorrect = syncer::SyncData::CreateLocalData(
|
| + incorrect_tag, incorrect_tag, password_data);
|
| + }
|
| +
|
| + // Set the correct local entry if needed.
|
| + if (!local_correct_ps ||
|
| + (newest_data != &local_correct_ps.value() &&
|
| + !AreNonSyncTagFieldsEqual(local_correct_ps.value(), *newest_data))) {
|
| + result.new_local_correct = PasswordFromSpecifics(*newest_data);
|
| + result.new_local_correct.value().origin = GURL(correct_origin);
|
| + result.new_local_correct.value().signon_realm = correct_signon_realm;
|
| + }
|
| +
|
| + // Set the incorrect local entry if needed.
|
| + if (!local_incorrect_ps ||
|
| + (newest_data != &local_incorrect_ps.value() &&
|
| + !AreNonSyncTagFieldsEqual(local_incorrect_ps.value(), *newest_data))) {
|
| + result.new_local_incorrect = PasswordFromSpecifics(*newest_data);
|
| + result.new_local_incorrect.value().origin = GURL(incorrect_origin);
|
| + result.new_local_incorrect.value().signon_realm = incorrect_signon_realm;
|
| + }
|
| + return result;
|
| +}
|
| +
|
| } // namespace
|
|
|
| struct PasswordSyncableService::SyncEntries {
|
| @@ -124,8 +336,9 @@ struct PasswordSyncableService::SyncEntries {
|
|
|
| PasswordSyncableService::PasswordSyncableService(
|
| PasswordStoreSync* password_store)
|
| - : password_store_(password_store), is_processing_sync_changes_(false) {
|
| -}
|
| + : password_store_(password_store),
|
| + clock_(new base::DefaultClock),
|
| + is_processing_sync_changes_(false) {}
|
|
|
| PasswordSyncableService::~PasswordSyncableService() {
|
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
| @@ -163,17 +376,12 @@ syncer::SyncMergeResult PasswordSyncableService::MergeDataAndStartSyncing(
|
| }
|
| merge_result.set_num_items_before_association(new_local_entries.size());
|
|
|
| + // Changes from Sync to be applied locally.
|
| SyncEntries sync_entries;
|
| // Changes from password db that need to be propagated to sync.
|
| syncer::SyncChangeList updated_db_entries;
|
| - for (syncer::SyncDataList::const_iterator sync_iter =
|
| - initial_sync_data.begin();
|
| - sync_iter != initial_sync_data.end(); ++sync_iter) {
|
| - CreateOrUpdateEntry(*sync_iter,
|
| - &new_local_entries,
|
| - &sync_entries,
|
| - &updated_db_entries);
|
| - }
|
| + MergeSyncDataWithLocalData(initial_sync_data, &new_local_entries,
|
| + &sync_entries, &updated_db_entries);
|
|
|
| for (PasswordEntryMap::iterator it = new_local_entries.begin();
|
| it != new_local_entries.end(); ++it) {
|
| @@ -237,7 +445,7 @@ syncer::SyncError PasswordSyncableService::ProcessSyncChanges(
|
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
| base::AutoReset<bool> processing_changes(&is_processing_sync_changes_, true);
|
| SyncEntries sync_entries;
|
| - base::Time time_now = base::Time::Now();
|
| + base::Time time_now = clock_->Now();
|
|
|
| for (syncer::SyncChangeList::const_iterator it = change_list.begin();
|
| it != change_list.end(); ++it) {
|
| @@ -250,6 +458,13 @@ syncer::SyncError PasswordSyncableService::ProcessSyncChanges(
|
| }
|
| AppendPasswordFromSpecifics(
|
| specifics.password().client_only_encrypted_data(), time_now, entries);
|
| + if (IsValidAndroidFacetURI(entries->back()->signon_realm)) {
|
| + // Fix the Android Autofill credentials if needed.
|
| + entries->back()->signon_realm =
|
| + GetCorrectAndroidSignonRealm(entries->back()->signon_realm);
|
| + entries->back()->origin =
|
| + GURL(GetCorrectAndroidSignonRealm(entries->back()->origin.spec()));
|
| + }
|
| }
|
|
|
| WriteToPasswordStore(sync_entries);
|
| @@ -339,21 +554,132 @@ void PasswordSyncableService::WriteToPasswordStore(const SyncEntries& entries) {
|
| password_store_->NotifyLoginsChanged(changes);
|
| }
|
|
|
| -// static
|
| +void PasswordSyncableService::MergeSyncDataWithLocalData(
|
| + const syncer::SyncDataList& sync_data,
|
| + PasswordEntryMap* unmatched_data_from_password_db,
|
| + SyncEntries* sync_entries,
|
| + syncer::SyncChangeList* updated_db_entries) {
|
| + std::map<std::string, const sync_pb::PasswordSpecificsData*> sync_data_map;
|
| + for (const auto& data : sync_data) {
|
| + const sync_pb::EntitySpecifics& specifics = data.GetSpecifics();
|
| + const sync_pb::PasswordSpecificsData* password_specifics =
|
| + &specifics.password().client_only_encrypted_data();
|
| + sync_data_map[MakePasswordSyncTag(*password_specifics)] =
|
| + password_specifics;
|
| + }
|
| + DCHECK_EQ(sync_data_map.size(), sync_data.size());
|
| +
|
| + for (auto it = sync_data_map.begin(); it != sync_data_map.end();) {
|
| + if (IsValidAndroidFacetURI(it->second->signon_realm())) {
|
| + // Perform deduplication of Android credentials saved in the wrong format.
|
| + // For each incorrect entry, a duplicate of it is created in the correct
|
| + // format, so Chrome can make use of it. The incorrect sync entries are
|
| + // not deleted for now.
|
| + std::string incorrect_tag = AndroidAutofillSyncTag(*it->second);
|
| + std::string correct_tag = AndroidCorrectSyncTag(*it->second);
|
| + auto it_sync_incorrect = sync_data_map.find(incorrect_tag);
|
| + auto it_sync_correct = sync_data_map.find(correct_tag);
|
| + auto it_local_data_correct =
|
| + unmatched_data_from_password_db->find(correct_tag);
|
| + auto it_local_data_incorrect =
|
| + unmatched_data_from_password_db->find(incorrect_tag);
|
| + if ((it != it_sync_incorrect && it != it_sync_correct) ||
|
| + (it_sync_incorrect == sync_data_map.end() &&
|
| + it_local_data_incorrect == unmatched_data_from_password_db->end())) {
|
| + // The current credential is in an unexpected format or incorrect
|
| + // credential don't exist. Just do what Sync would normally do.
|
| + CreateOrUpdateEntry(*it->second, unmatched_data_from_password_db,
|
| + sync_entries, updated_db_entries);
|
| + ++it;
|
| + } else {
|
| + AndroidMergeResult result = Perform4WayMergeAndroidCredentials(
|
| + it_sync_correct == sync_data_map.end() ? nullptr
|
| + : it_sync_correct->second,
|
| + it_sync_incorrect == sync_data_map.end()
|
| + ? nullptr
|
| + : it_sync_incorrect->second,
|
| + it_local_data_correct == unmatched_data_from_password_db->end()
|
| + ? nullptr
|
| + : it_local_data_correct->second,
|
| + it_local_data_incorrect == unmatched_data_from_password_db->end()
|
| + ? nullptr
|
| + : it_local_data_incorrect->second);
|
| + // Add or update the correct local entry.
|
| + if (result.new_local_correct) {
|
| + auto* local_changes = sync_entries->EntriesForChangeType(
|
| + it_local_data_correct == unmatched_data_from_password_db->end()
|
| + ? syncer::SyncChange::ACTION_ADD
|
| + : syncer::SyncChange::ACTION_UPDATE);
|
| + local_changes->push_back(base::MakeUnique<autofill::PasswordForm>(
|
| + result.new_local_correct.value()));
|
| + local_changes->back()->date_synced = clock_->Now();
|
| + }
|
| + // Add or update the incorrect local entry.
|
| + if (result.new_local_incorrect) {
|
| + auto* local_changes = sync_entries->EntriesForChangeType(
|
| + it_local_data_incorrect == unmatched_data_from_password_db->end()
|
| + ? syncer::SyncChange::ACTION_ADD
|
| + : syncer::SyncChange::ACTION_UPDATE);
|
| + local_changes->push_back(base::MakeUnique<autofill::PasswordForm>(
|
| + result.new_local_incorrect.value()));
|
| + local_changes->back()->date_synced = clock_->Now();
|
| + }
|
| + if (it_local_data_correct != unmatched_data_from_password_db->end())
|
| + unmatched_data_from_password_db->erase(it_local_data_correct);
|
| + if (it_local_data_incorrect != unmatched_data_from_password_db->end())
|
| + unmatched_data_from_password_db->erase(it_local_data_incorrect);
|
| + // Add or update the correct sync entry.
|
| + if (result.new_android_correct) {
|
| + updated_db_entries->push_back(
|
| + syncer::SyncChange(FROM_HERE,
|
| + it_sync_correct == sync_data_map.end()
|
| + ? syncer::SyncChange::ACTION_ADD
|
| + : syncer::SyncChange::ACTION_UPDATE,
|
| + result.new_android_correct.value()));
|
| + }
|
| + // Add or update the Android Autofill sync entry.
|
| + if (result.new_android_incorrect) {
|
| + updated_db_entries->push_back(
|
| + syncer::SyncChange(FROM_HERE,
|
| + it_sync_incorrect == sync_data_map.end()
|
| + ? syncer::SyncChange::ACTION_ADD
|
| + : syncer::SyncChange::ACTION_UPDATE,
|
| + result.new_android_incorrect.value()));
|
| + }
|
| + bool increment = true;
|
| + for (auto sync_data_it : {it_sync_incorrect, it_sync_correct}) {
|
| + if (sync_data_it != sync_data_map.end()) {
|
| + if (sync_data_it == it) {
|
| + it = sync_data_map.erase(it);
|
| + increment = false;
|
| + } else {
|
| + sync_data_map.erase(sync_data_it);
|
| + }
|
| + }
|
| + }
|
| + if (increment)
|
| + ++it;
|
| + }
|
| + } else {
|
| + // Not Android.
|
| + CreateOrUpdateEntry(*it->second, unmatched_data_from_password_db,
|
| + sync_entries, updated_db_entries);
|
| + ++it;
|
| + }
|
| + }
|
| +}
|
| +
|
| void PasswordSyncableService::CreateOrUpdateEntry(
|
| - const syncer::SyncData& data,
|
| + const sync_pb::PasswordSpecificsData& password_specifics,
|
| PasswordEntryMap* unmatched_data_from_password_db,
|
| SyncEntries* sync_entries,
|
| syncer::SyncChangeList* updated_db_entries) {
|
| - const sync_pb::EntitySpecifics& specifics = data.GetSpecifics();
|
| - const sync_pb::PasswordSpecificsData& password_specifics(
|
| - specifics.password().client_only_encrypted_data());
|
| std::string tag = MakePasswordSyncTag(password_specifics);
|
|
|
| // Check whether the data from sync is already in the password store.
|
| PasswordEntryMap::iterator existing_local_entry_iter =
|
| unmatched_data_from_password_db->find(tag);
|
| - base::Time time_now = base::Time::Now();
|
| + base::Time time_now = clock_->Now();
|
| if (existing_local_entry_iter == unmatched_data_from_password_db->end()) {
|
| // The sync data is not in the password store, so we need to create it in
|
| // the password store. Add the entry to the new_entries list.
|
| @@ -401,31 +727,7 @@ syncer::SyncData SyncDataFromPassword(
|
| sync_pb::EntitySpecifics password_data;
|
| sync_pb::PasswordSpecificsData* password_specifics =
|
| password_data.mutable_password()->mutable_client_only_encrypted_data();
|
| -#define CopyField(field) password_specifics->set_##field(password_form.field)
|
| -#define CopyStringField(field) \
|
| - password_specifics->set_##field(base::UTF16ToUTF8(password_form.field))
|
| - CopyField(scheme);
|
| - CopyField(signon_realm);
|
| - password_specifics->set_origin(password_form.origin.spec());
|
| - password_specifics->set_action(password_form.action.spec());
|
| - CopyStringField(username_element);
|
| - CopyStringField(password_element);
|
| - CopyStringField(username_value);
|
| - CopyStringField(password_value);
|
| - CopyField(preferred);
|
| - password_specifics->set_date_created(
|
| - password_form.date_created.ToInternalValue());
|
| - password_specifics->set_blacklisted(password_form.blacklisted_by_user);
|
| - CopyField(type);
|
| - CopyField(times_used);
|
| - CopyStringField(display_name);
|
| - password_specifics->set_avatar_url(password_form.icon_url.spec());
|
| - password_specifics->set_federation_url(
|
| - password_form.federation_origin.unique()
|
| - ? std::string()
|
| - : password_form.federation_origin.Serialize());
|
| -#undef CopyStringField
|
| -#undef CopyField
|
| + PasswordSpecificsFromPassword(password_form, password_specifics);
|
|
|
| std::string tag = MakePasswordSyncTag(*password_specifics);
|
| return syncer::SyncData::CreateLocalData(tag, tag, password_data);
|
|
|