| Index: components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service_unittest.cc
|
| diff --git a/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service_unittest.cc b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service_unittest.cc
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..2c851f2fa47afe4a79e69659ccbf405883318ca1
|
| --- /dev/null
|
| +++ b/components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service_unittest.cc
|
| @@ -0,0 +1,454 @@
|
| +// Copyright 2015 The Chromium Authors. All rights reserved.
|
| +// Use of this source code is governed by a BSD-style license that can be
|
| +// found in the LICENSE file.
|
| +
|
| +#include "components/autofill/core/browser/webdata/autofill_wallet_metadata_syncable_service.h"
|
| +
|
| +#include "base/strings/string_number_conversions.h"
|
| +#include "base/time/time.h"
|
| +#include "components/autofill/core/browser/autofill_profile.h"
|
| +#include "components/autofill/core/browser/credit_card.h"
|
| +#include "sync/api/sync_change.h"
|
| +#include "sync/api/sync_change_processor_wrapper_for_test.h"
|
| +#include "sync/api/sync_error_factory_mock.h"
|
| +#include "sync/protocol/autofill_specifics.pb.h"
|
| +#include "sync/protocol/sync.pb.h"
|
| +#include "testing/gmock/include/gmock/gmock.h"
|
| +#include "testing/gtest/include/gtest/gtest.h"
|
| +
|
| +namespace autofill {
|
| +namespace {
|
| +
|
| +using testing::DoAll;
|
| +using testing::NiceMock;
|
| +using testing::Return;
|
| +using testing::_;
|
| +
|
| +ACTION_P(GetPointersTo, data_list) {
|
| + for (auto& datum : *data_list)
|
| + arg0->push_back(&datum);
|
| +}
|
| +
|
| +// A syncable service for Wallet metadata that mocks out disk IO for server
|
| +// profiles and cards.
|
| +class MockAutofillWalletMetadataSyncableService
|
| + : public AutofillWalletMetadataSyncableService {
|
| + public:
|
| + MockAutofillWalletMetadataSyncableService()
|
| + : AutofillWalletMetadataSyncableService(nullptr, std::string()) {
|
| + ON_CALL(*this, GetServerProfiles(_))
|
| + .WillByDefault(DoAll(GetPointersTo(&server_profiles_), Return(true)));
|
| +
|
| + ON_CALL(*this, GetServerCreditCards(_))
|
| + .WillByDefault(DoAll(GetPointersTo(&server_cards_), Return(true)));
|
| +
|
| + ON_CALL(*this, UpdateServerAddressUsageStats(_))
|
| + .WillByDefault(Return(true));
|
| +
|
| + ON_CALL(*this, UpdateServerCardUsageStats(_)).WillByDefault(Return(true));
|
| + }
|
| +
|
| + ~MockAutofillWalletMetadataSyncableService() override {}
|
| +
|
| + MOCK_CONST_METHOD1(GetServerProfiles, bool(std::vector<AutofillProfile*>*));
|
| + MOCK_CONST_METHOD1(GetServerCreditCards, bool(std::vector<CreditCard*>*));
|
| + MOCK_METHOD1(UpdateServerAddressUsageStats, bool(const AutofillProfile&));
|
| + MOCK_METHOD1(UpdateServerCardUsageStats, bool(const CreditCard&));
|
| +
|
| + // "Saves on disk" a server profile with the given |server_id|, |use_count|,
|
| + // and |use_date|.
|
| + void SaveServerProfile(const std::string& server_id,
|
| + int64 use_count,
|
| + int64 use_date) {
|
| + AutofillProfile profile(AutofillProfile::SERVER_PROFILE, server_id);
|
| + profile.set_use_count(use_count);
|
| + profile.set_use_date(base::Time::FromInternalValue(use_date));
|
| +
|
| + for (auto it = server_profiles_.begin(); it != server_profiles_.end();
|
| + ++it) {
|
| + if (it->server_id() == server_id) {
|
| + *it = profile;
|
| + return;
|
| + }
|
| + }
|
| +
|
| + server_profiles_.push_back(profile);
|
| + }
|
| +
|
| + // "Saves on disk" a server card with the given |server_id|, |use_count|, and
|
| + // |use_date|.
|
| + void SaveServerCard(const std::string& server_id,
|
| + int64 use_count,
|
| + int64 use_date) {
|
| + CreditCard card(CreditCard::MASKED_SERVER_CARD, server_id);
|
| + card.set_use_count(use_count);
|
| + card.set_use_date(base::Time::FromInternalValue(use_date));
|
| +
|
| + for (auto it = server_cards_.begin(); it != server_cards_.end(); ++it) {
|
| + if (it->server_id() == server_id) {
|
| + *it = card;
|
| + return;
|
| + }
|
| + }
|
| +
|
| + server_cards_.push_back(card);
|
| + }
|
| +
|
| + private:
|
| + std::vector<AutofillProfile> server_profiles_;
|
| + std::vector<CreditCard> server_cards_;
|
| +
|
| + DISALLOW_COPY_AND_ASSIGN(MockAutofillWalletMetadataSyncableService);
|
| +};
|
| +
|
| +// Verify that nothing is sent to the sync server when there's no metadata on
|
| +// disk.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, NoMetadataToReturn) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> service;
|
| + EXPECT_TRUE(service.GetAllSyncData(syncer::AUTOFILL_WALLET_METADATA).empty());
|
| +}
|
| +
|
| +// Verify that all metadata from disk is sent to the sync server.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, ReturnAllMetadata) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> service;
|
| + service.SaveServerProfile("data", 1, 2);
|
| + service.SaveServerCard("data", 1, 2);
|
| +
|
| + syncer::SyncDataList data_list =
|
| + service.GetAllSyncData(syncer::AUTOFILL_WALLET_METADATA);
|
| +
|
| + ASSERT_EQ(2UL, data_list.size());
|
| + EXPECT_NE(syncer::SyncDataLocal(data_list[0]).GetTag(),
|
| + syncer::SyncDataLocal(data_list[1]).GetTag());
|
| +
|
| + for (const syncer::SyncData& datum : data_list) {
|
| + EXPECT_TRUE(datum.IsValid());
|
| + EXPECT_TRUE("address-data" == syncer::SyncDataLocal(datum).GetTag() ||
|
| + "card-data" == syncer::SyncDataLocal(datum).GetTag());
|
| + EXPECT_EQ(syncer::AUTOFILL_WALLET_METADATA, datum.GetDataType());
|
| + EXPECT_EQ("data", datum.GetSpecifics().wallet_metadata().id());
|
| + EXPECT_EQ(1, datum.GetSpecifics().wallet_metadata().use_count());
|
| + EXPECT_EQ(2, datum.GetSpecifics().wallet_metadata().use_date());
|
| + }
|
| +}
|
| +
|
| +// Verify that nothing is written to disk for empty sync changes.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, ProcessEmptyChanges) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> service;
|
| + service.SaveServerProfile("addr", 1, 2);
|
| + service.SaveServerCard("card", 3, 4);
|
| +
|
| + EXPECT_CALL(service, UpdateServerAddressUsageStats(_)).Times(0);
|
| + EXPECT_CALL(service, UpdateServerCardUsageStats(_)).Times(0);
|
| +
|
| + EXPECT_FALSE(
|
| + service.ProcessSyncChanges(FROM_HERE, syncer::SyncChangeList()).IsSet());
|
| +}
|
| +
|
| +syncer::SyncChange BuildChange(
|
| + syncer::SyncChange::SyncChangeType change_type,
|
| + sync_pb::WalletMetadataSpecifics::Type metadata_type,
|
| + const std::string& server_id,
|
| + int64 use_count,
|
| + int64 use_date) {
|
| + sync_pb::EntitySpecifics entity;
|
| + entity.mutable_wallet_metadata()->set_type(metadata_type);
|
| + entity.mutable_wallet_metadata()->set_id(server_id);
|
| + entity.mutable_wallet_metadata()->set_use_count(use_count);
|
| + entity.mutable_wallet_metadata()->set_use_date(use_date);
|
| + return syncer::SyncChange(
|
| + FROM_HERE, change_type,
|
| + syncer::SyncData::CreateLocalData(server_id, server_id, entity));
|
| +}
|
| +
|
| +MATCHER_P3(Matches,
|
| + server_id,
|
| + use_count,
|
| + use_date,
|
| + std::string(negation ? "isn't" : "is") + " server_id=" + server_id +
|
| + " use_count=" +
|
| + base::IntToString(use_count) +
|
| + " use_date=" +
|
| + base::IntToString(use_date)) {
|
| + *result_listener << "where server_id=" << arg.server_id()
|
| + << " use_count=" << arg.use_count()
|
| + << " use_date=" << arg.use_date().ToInternalValue();
|
| + return arg.server_id() == server_id &&
|
| + arg.use_count() == static_cast<size_t>(use_count) &&
|
| + arg.use_date() == base::Time::FromInternalValue(use_date);
|
| +}
|
| +
|
| +// Verify that sync changes are written to disk.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, ProcessChanges) {
|
| + syncer::SyncChangeList changes;
|
| + changes.push_back(BuildChange(syncer::SyncChange::ACTION_ADD,
|
| + sync_pb::WalletMetadataSpecifics::ADDRESS,
|
| + "addr1", 1, 2));
|
| + changes.push_back(BuildChange(syncer::SyncChange::ACTION_UPDATE,
|
| + sync_pb::WalletMetadataSpecifics::ADDRESS,
|
| + "addr2", 3, 4));
|
| + changes.push_back(BuildChange(syncer::SyncChange::ACTION_ADD,
|
| + sync_pb::WalletMetadataSpecifics::CARD, "card1",
|
| + 5, 6));
|
| + changes.push_back(BuildChange(syncer::SyncChange::ACTION_UPDATE,
|
| + sync_pb::WalletMetadataSpecifics::CARD, "card2",
|
| + 7, 8));
|
| +
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> service;
|
| + service.SaveServerProfile("addr1", 0, 0);
|
| + service.SaveServerProfile("addr2", 0, 0);
|
| + service.SaveServerCard("card1", 0, 0);
|
| + service.SaveServerCard("card2", 0, 0);
|
| +
|
| + EXPECT_CALL(service, UpdateServerAddressUsageStats(Matches("addr1", 1, 2)));
|
| + EXPECT_CALL(service, UpdateServerAddressUsageStats(Matches("addr2", 3, 4)));
|
| + EXPECT_CALL(service, UpdateServerCardUsageStats(Matches("card1", 5, 6)));
|
| + EXPECT_CALL(service, UpdateServerCardUsageStats(Matches("card2", 7, 8)));
|
| +
|
| + EXPECT_FALSE(service.ProcessSyncChanges(FROM_HERE, changes).IsSet());
|
| +}
|
| +
|
| +// Verify that local metadata is not changed if the sync server requests
|
| +// deletion. (Metadata life-cycle is tied to the corresponding Wallet data.)
|
| +TEST(AutofillWalletMetadataSyncableServiceTest,
|
| + IgnoreSyncCommandsToDeleteMetadata) {
|
| + syncer::SyncChangeList changes;
|
| + changes.push_back(BuildChange(syncer::SyncChange::ACTION_DELETE,
|
| + sync_pb::WalletMetadataSpecifics::ADDRESS,
|
| + "addr", 1, 2));
|
| + changes.push_back(BuildChange(syncer::SyncChange::ACTION_DELETE,
|
| + sync_pb::WalletMetadataSpecifics::CARD, "card",
|
| + 3, 4));
|
| +
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> service;
|
| + service.SaveServerProfile("addr", 0, 0);
|
| + service.SaveServerCard("card", 0, 0);
|
| +
|
| + EXPECT_CALL(service, UpdateServerAddressUsageStats(_)).Times(0);
|
| + EXPECT_CALL(service, UpdateServerCardUsageStats(_)).Times(0);
|
| +
|
| + EXPECT_FALSE(service.ProcessSyncChanges(FROM_HERE, changes).IsSet());
|
| +}
|
| +
|
| +// Verify that incoming sync messages do not write metadata for non-existent
|
| +// data.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest,
|
| + IgnoreOrphanMetadataInSyncChanges) {
|
| + syncer::SyncChangeList changes;
|
| + changes.push_back(BuildChange(syncer::SyncChange::ACTION_ADD,
|
| + sync_pb::WalletMetadataSpecifics::ADDRESS,
|
| + "addr", 1, 2));
|
| + changes.push_back(BuildChange(syncer::SyncChange::ACTION_ADD,
|
| + sync_pb::WalletMetadataSpecifics::CARD, "card",
|
| + 3, 4));
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> service;
|
| +
|
| + EXPECT_CALL(service, UpdateServerAddressUsageStats(_)).Times(0);
|
| + EXPECT_CALL(service, UpdateServerCardUsageStats(_)).Times(0);
|
| +
|
| + EXPECT_FALSE(service.ProcessSyncChanges(FROM_HERE, changes).IsSet());
|
| +}
|
| +
|
| +void MergeMetadata(MockAutofillWalletMetadataSyncableService* local,
|
| + MockAutofillWalletMetadataSyncableService* remote) {
|
| + scoped_ptr<syncer::SyncErrorFactoryMock> errors(
|
| + new syncer::SyncErrorFactoryMock);
|
| + EXPECT_CALL(*errors, CreateAndUploadError(_, _)).Times(0);
|
| + EXPECT_FALSE(
|
| + local->MergeDataAndStartSyncing(
|
| + syncer::AUTOFILL_WALLET_METADATA,
|
| + remote->GetAllSyncData(syncer::AUTOFILL_WALLET_METADATA),
|
| + scoped_ptr<syncer::SyncChangeProcessor>(
|
| + new syncer::SyncChangeProcessorWrapperForTest(remote)),
|
| + errors.Pass())
|
| + .error()
|
| + .IsSet());
|
| +}
|
| +
|
| +// Verify that no writes happen when sync server sends no metadata.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, NoDataToMerge) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> local;
|
| + local.SaveServerProfile("addr", 1, 2);
|
| + local.SaveServerCard("card", 3, 4);
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> remote;
|
| +
|
| + EXPECT_CALL(local, UpdateServerAddressUsageStats(_)).Times(0);
|
| + EXPECT_CALL(local, UpdateServerCardUsageStats(_)).Times(0);
|
| +
|
| + MergeMetadata(&local, &remote);
|
| +}
|
| +
|
| +// Verify that no writes happen when sync server sends metadata that is
|
| +// identical to the local metadata.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, IdenticalDataMerge) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> local;
|
| + local.SaveServerProfile("addr", 1, 2);
|
| + local.SaveServerCard("card", 3, 4);
|
| +
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> remote;
|
| + remote.SaveServerProfile("addr", 1, 2);
|
| + remote.SaveServerCard("card", 3, 4);
|
| +
|
| + EXPECT_CALL(local, UpdateServerAddressUsageStats(_)).Times(0);
|
| + EXPECT_CALL(local, UpdateServerCardUsageStats(_)).Times(0);
|
| +
|
| + EXPECT_CALL(remote, UpdateServerAddressUsageStats(_)).Times(0);
|
| + EXPECT_CALL(remote, UpdateServerCardUsageStats(_)).Times(0);
|
| +
|
| + MergeMetadata(&local, &remote);
|
| +}
|
| +
|
| +// Verify that the larger use count and later use date overwrite the smaller use
|
| +// count and earlier use date when merging metadata.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, MergePicksMaxUseCountDate) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> local;
|
| + local.SaveServerProfile("addr", 50, 100);
|
| + local.SaveServerCard("card", 100, 50);
|
| +
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> remote;
|
| + remote.SaveServerProfile("addr", 100, 50);
|
| + remote.SaveServerCard("card", 50, 100);
|
| +
|
| + EXPECT_CALL(local, UpdateServerAddressUsageStats(Matches("addr", 100, 100)));
|
| + EXPECT_CALL(local, UpdateServerCardUsageStats(Matches("card", 100, 100)));
|
| +
|
| + EXPECT_CALL(remote, UpdateServerAddressUsageStats(Matches("addr", 100, 100)));
|
| + EXPECT_CALL(remote, UpdateServerCardUsageStats(Matches("card", 100, 100)));
|
| +
|
| + MergeMetadata(&local, &remote);
|
| +}
|
| +
|
| +// Verify that initial merge does not write metadata for non-existent data.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest,
|
| + IgnoreOrphanMetadataInInitialMerge) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> local;
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> remote;
|
| + remote.SaveServerProfile("addr", 1, 2);
|
| + remote.SaveServerCard("card", 3, 4);
|
| +
|
| + EXPECT_CALL(local, UpdateServerAddressUsageStats(_)).Times(0);
|
| + EXPECT_CALL(local, UpdateServerCardUsageStats(_)).Times(0);
|
| +
|
| + MergeMetadata(&local, &remote);
|
| +}
|
| +
|
| +// Verify that updating Wallet data and metadata will send a message to the sync
|
| +// server.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, WalletDataUpdateSyncsMetadata) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> local;
|
| + local.SaveServerProfile("addr", 0, 0);
|
| + local.SaveServerCard("card", 0, 0);
|
| +
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> remote;
|
| + remote.SaveServerProfile("addr", 0, 0);
|
| + remote.SaveServerCard("card", 0, 0);
|
| +
|
| + MergeMetadata(&local, &remote);
|
| +
|
| + AutofillProfile profile(AutofillProfile::SERVER_PROFILE, "addr");
|
| + profile.set_use_count(1);
|
| + profile.set_use_date(base::Time::FromInternalValue(2));
|
| +
|
| + CreditCard card(CreditCard::MASKED_SERVER_CARD, "card");
|
| + card.set_use_count(3);
|
| + card.set_use_date(base::Time::FromInternalValue(4));
|
| +
|
| + EXPECT_CALL(remote, UpdateServerAddressUsageStats(Matches("addr", 1, 2)));
|
| + EXPECT_CALL(remote, UpdateServerCardUsageStats(Matches("card", 3, 4)));
|
| +
|
| + local.AutofillProfileChanged(AutofillProfileChange(
|
| + AutofillProfileChange::UPDATE, profile.guid(), &profile));
|
| + local.CreditCardChanged(
|
| + CreditCardChange(CreditCardChange::UPDATE, card.guid(), &card));
|
| +}
|
| +
|
| +// Verify that updating a non-Wallet profile or credit card will not send
|
| +// metadata to the sync server.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, IgnoreNonWalletDataUpdate) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> local;
|
| + local.SaveServerProfile("addr", 0, 0);
|
| + local.SaveServerCard("card", 0, 0);
|
| +
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> remote;
|
| + remote.SaveServerProfile("addr", 0, 0);
|
| + remote.SaveServerCard("card", 0, 0);
|
| +
|
| + MergeMetadata(&local, &remote);
|
| +
|
| + AutofillProfile profile;
|
| + profile.set_use_count(1);
|
| + profile.set_use_date(base::Time::FromInternalValue(2));
|
| +
|
| + CreditCard card;
|
| + card.set_use_count(3);
|
| + card.set_use_date(base::Time::FromInternalValue(4));
|
| +
|
| + EXPECT_CALL(remote, UpdateServerAddressUsageStats(_)).Times(0);
|
| + EXPECT_CALL(remote, UpdateServerCardUsageStats(_)).Times(0);
|
| +
|
| + local.AutofillProfileChanged(AutofillProfileChange(
|
| + AutofillProfileChange::UPDATE, profile.guid(), &profile));
|
| + local.CreditCardChanged(
|
| + CreditCardChange(CreditCardChange::UPDATE, card.guid(), &card));
|
| +}
|
| +
|
| +// Verify that deleting or adding an individual Wallet address or credit card is
|
| +// ignored. (Sync should be notified only of "multiple autofill data changes"
|
| +// sent by the Wallet sync service.)
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, IgnoreIndividualDataAddDelete) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> local;
|
| + local.SaveServerProfile("addr1", 0, 0);
|
| + local.SaveServerProfile("addr2", 0, 0);
|
| + local.SaveServerCard("card1", 0, 0);
|
| + local.SaveServerCard("card2", 0, 0);
|
| +
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> remote;
|
| + remote.SaveServerProfile("addr1", 0, 0);
|
| + remote.SaveServerProfile("addr2", 0, 0);
|
| + remote.SaveServerCard("card1", 0, 0);
|
| + remote.SaveServerCard("card2", 0, 0);
|
| +
|
| + MergeMetadata(&local, &remote);
|
| +
|
| + AutofillProfile profile1(AutofillProfile::SERVER_PROFILE, "addr1");
|
| + AutofillProfile profile2(AutofillProfile::SERVER_PROFILE, "addr2");
|
| + CreditCard card1(CreditCard::MASKED_SERVER_CARD, "card1");
|
| + CreditCard card2(CreditCard::FULL_SERVER_CARD, "card2");
|
| +
|
| + EXPECT_CALL(remote, UpdateServerAddressUsageStats(_)).Times(0);
|
| + EXPECT_CALL(remote, UpdateServerCardUsageStats(_)).Times(0);
|
| +
|
| + local.AutofillProfileChanged(AutofillProfileChange(
|
| + AutofillProfileChange::ADD, profile1.guid(), &profile1));
|
| + local.AutofillProfileChanged(AutofillProfileChange(
|
| + AutofillProfileChange::REMOVE, profile2.guid(), nullptr));
|
| + local.CreditCardChanged(
|
| + CreditCardChange(CreditCardChange::ADD, card1.guid(), &card1));
|
| + local.CreditCardChanged(
|
| + CreditCardChange(CreditCardChange::REMOVE, card2.guid(), nullptr));
|
| +}
|
| +
|
| +// Verify that the sync server is notified of new metadata when Wallet data is
|
| +// added.
|
| +TEST(AutofillWalletMetadataSyncableServiceTest, NotifySyncServerNewMetadata) {
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> local;
|
| + local.SaveServerProfile("addr", 0, 0);
|
| + local.SaveServerCard("card", 0, 0);
|
| +
|
| + NiceMock<MockAutofillWalletMetadataSyncableService> remote;
|
| + remote.SaveServerProfile("addr", 0, 0);
|
| + remote.SaveServerCard("card", 0, 0);
|
| +
|
| + MergeMetadata(&local, &remote);
|
| +
|
| + local.SaveServerProfile("addr", 1, 2);
|
| + local.SaveServerCard("card", 3, 4);
|
| +
|
| + EXPECT_CALL(remote, UpdateServerAddressUsageStats(Matches("addr", 1, 2)));
|
| + EXPECT_CALL(remote, UpdateServerCardUsageStats(Matches("card", 3, 4)));
|
| +
|
| + local.AutofillMultipleChanged();
|
| +}
|
| +
|
| +} // namespace
|
| +} // namespace autofill
|
|
|