Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1032)

Side by Side Diff: components/autofill/core/browser/autofill_profile.cc

Issue 2110563002: Use AutofillProfileComparator in place of ad-hoc merge logic. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@merge
Patch Set: Rebase Created 4 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 // Copyright 2013 The Chromium Authors. All rights reserved. 1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "components/autofill/core/browser/autofill_profile.h" 5 #include "components/autofill/core/browser/autofill_profile.h"
6 6
7 #include <algorithm> 7 #include <algorithm>
8 #include <functional> 8 #include <functional>
9 #include <map> 9 #include <map>
10 #include <memory> 10 #include <memory>
(...skipping 171 matching lines...) Expand 10 before | Expand all | Expand 10 after
182 for (const ServerFieldType& it : *suggested_fields) { 182 for (const ServerFieldType& it : *suggested_fields) {
183 if (it != excluded_field && 183 if (it != excluded_field &&
184 GetStorableTypeCollapsingGroups(it) == effective_excluded_type) { 184 GetStorableTypeCollapsingGroups(it) == effective_excluded_type) {
185 distinguishing_fields->push_back(effective_excluded_type); 185 distinguishing_fields->push_back(effective_excluded_type);
186 break; 186 break;
187 } 187 }
188 } 188 }
189 } 189 }
190 } 190 }
191 191
192 // Collapse compound field types to their "full" type. I.e. First name
193 // collapses to full name, area code collapses to full phone, etc.
194 void CollapseCompoundFieldTypes(ServerFieldTypeSet* type_set) {
195 ServerFieldTypeSet collapsed_set;
196 for (const auto& it : *type_set) {
197 switch (it) {
198 case NAME_FIRST:
199 case NAME_MIDDLE:
200 case NAME_LAST:
201 case NAME_MIDDLE_INITIAL:
202 case NAME_FULL:
203 case NAME_SUFFIX:
204 collapsed_set.insert(NAME_FULL);
205 break;
206
207 case PHONE_HOME_NUMBER:
208 case PHONE_HOME_CITY_CODE:
209 case PHONE_HOME_COUNTRY_CODE:
210 case PHONE_HOME_CITY_AND_NUMBER:
211 case PHONE_HOME_WHOLE_NUMBER:
212 collapsed_set.insert(PHONE_HOME_WHOLE_NUMBER);
213 break;
214
215 default:
216 collapsed_set.insert(it);
217 }
218 }
219 std::swap(*type_set, collapsed_set);
220 }
221
222 } // namespace 192 } // namespace
223 193
224 AutofillProfile::AutofillProfile(const std::string& guid, 194 AutofillProfile::AutofillProfile(const std::string& guid,
225 const std::string& origin) 195 const std::string& origin)
226 : AutofillDataModel(guid, origin), 196 : AutofillDataModel(guid, origin),
227 record_type_(LOCAL_PROFILE), 197 record_type_(LOCAL_PROFILE),
228 phone_number_(this) { 198 phone_number_(this) {
229 } 199 }
230 200
231 AutofillProfile::AutofillProfile(RecordType type, const std::string& server_id) 201 AutofillProfile::AutofillProfile(RecordType type, const std::string& server_id)
(...skipping 176 matching lines...) Expand 10 before | Expand all | Expand 10 after
408 } 378 }
409 379
410 bool AutofillProfile::operator==(const AutofillProfile& profile) const { 380 bool AutofillProfile::operator==(const AutofillProfile& profile) const {
411 return guid() == profile.guid() && EqualsSansGuid(profile); 381 return guid() == profile.guid() && EqualsSansGuid(profile);
412 } 382 }
413 383
414 bool AutofillProfile::operator!=(const AutofillProfile& profile) const { 384 bool AutofillProfile::operator!=(const AutofillProfile& profile) const {
415 return !operator==(profile); 385 return !operator==(profile);
416 } 386 }
417 387
418 const base::string16 AutofillProfile::PrimaryValue(
419 const std::string& app_locale) const {
420 std::vector<base::string16> primary_values{
421 GetInfo(AutofillType(NAME_FIRST), app_locale),
422 GetInfo(AutofillType(NAME_LAST), app_locale),
423 GetInfo(AutofillType(ADDRESS_HOME_LINE1), app_locale),
424 GetInfo(AutofillType(ADDRESS_HOME_CITY), app_locale)};
425 return CanonicalizeProfileString(
426 base::JoinString(primary_values, base::UTF8ToUTF16(" ")));
427 }
428
429 bool AutofillProfile::IsSubsetOf(const AutofillProfile& profile, 388 bool AutofillProfile::IsSubsetOf(const AutofillProfile& profile,
430 const std::string& app_locale) const { 389 const std::string& app_locale) const {
431 ServerFieldTypeSet types; 390 ServerFieldTypeSet types;
432 GetSupportedTypes(&types); 391 GetSupportedTypes(&types);
433 return IsSubsetOfForFieldSet(profile, app_locale, types); 392 return IsSubsetOfForFieldSet(profile, app_locale, types);
434 } 393 }
435 394
436 bool AutofillProfile::IsSubsetOfForFieldSet( 395 bool AutofillProfile::IsSubsetOfForFieldSet(
437 const AutofillProfile& profile, 396 const AutofillProfile& profile,
438 const std::string& app_locale, 397 const std::string& app_locale,
439 const ServerFieldTypeSet& types) const { 398 const ServerFieldTypeSet& types) const {
440 std::unique_ptr<l10n::CaseInsensitiveCompare> compare; 399 AutofillProfileComparator comparator(app_locale);
441 400
442 for (ServerFieldType type : types) { 401 for (ServerFieldType type : types) {
443 base::string16 value = GetRawInfo(type); 402 base::string16 value = GetRawInfo(type);
444 if (value.empty()) 403 if (value.empty())
445 continue; 404 continue;
446 405
447 if (type == NAME_FULL || type == ADDRESS_HOME_STREET_ADDRESS) { 406 if (type == NAME_FULL || type == ADDRESS_HOME_STREET_ADDRESS) {
448 // Ignore the compound "full name" field type. We are only interested in 407 // Ignore the compound "full name" field type. We are only interested in
449 // comparing the constituent parts. For example, if |this| has a middle 408 // comparing the constituent parts. For example, if |this| has a middle
450 // name saved, but |profile| lacks one, |profile| could still be a subset 409 // name saved, but |profile| lacks one, |profile| could still be a subset
451 // of |this|. Likewise, ignore the compound "street address" type, as we 410 // of |this|. Likewise, ignore the compound "street address" type, as we
452 // are only interested in matching line-by-line. 411 // are only interested in matching line-by-line.
453 continue; 412 continue;
454 } else if (AutofillType(type).group() == PHONE_HOME) { 413 } else if (AutofillType(type).group() == PHONE_HOME) {
455 // Phone numbers should be canonicalized prior to being compared. 414 // Phone numbers should be canonicalized prior to being compared.
456 if (type != PHONE_HOME_WHOLE_NUMBER) { 415 if (type != PHONE_HOME_WHOLE_NUMBER) {
457 continue; 416 continue;
458 } else if (!i18n::PhoneNumbersMatch( 417 } else if (!i18n::PhoneNumbersMatch(
459 value, profile.GetRawInfo(type), 418 value, profile.GetRawInfo(type),
460 base::UTF16ToASCII(GetRawInfo(ADDRESS_HOME_COUNTRY)), 419 base::UTF16ToASCII(GetRawInfo(ADDRESS_HOME_COUNTRY)),
461 app_locale)) { 420 app_locale)) {
462 return false; 421 return false;
463 } 422 }
464 } else { 423 } else {
465 if (!compare) 424 const base::string16 this_value =
466 compare.reset(new l10n::CaseInsensitiveCompare()); 425 comparator.NormalizeForComparison(value);
467 if (!compare->StringsEqual(value, profile.GetRawInfo(type))) 426 const base::string16 that_value =
427 comparator.NormalizeForComparison(profile.GetRawInfo(type));
428 if (this_value != that_value)
468 return false; 429 return false;
469 } 430 }
470 } 431 }
471 432
472 return true; 433 return true;
473 } 434 }
474 435
475 bool AutofillProfile::OverwriteName(const NameInfo& imported_name, 436 bool AutofillProfile::MergeDataFrom(const AutofillProfile& profile,
476 const std::string& app_locale) { 437 const std::string& app_locale) {
477 // Check if the names parts are equal. 438 // Verified profiles should never be overwritten with unverified data.
478 if (name_.ParsedNamesAreEqual(imported_name)) { 439 DCHECK(!IsVerified() || profile.IsVerified());
479 // If the current |name_| has an empty NAME_FULL but the the |imported_name| 440 AutofillProfileComparator comparator(app_locale);
480 // has not, overwrite only NAME_FULL. 441 DCHECK(comparator.AreMergeable(*this, profile));
481 if (name_.GetRawInfo(NAME_FULL).empty() && 442
482 !imported_name.GetRawInfo(NAME_FULL).empty()) { 443 NameInfo name;
483 name_.SetRawInfo(NAME_FULL, imported_name.GetRawInfo(NAME_FULL)); 444 EmailInfo email;
484 return true; 445 CompanyInfo company;
485 } 446 PhoneNumber phone_number(this);
447 Address address;
448
449 // The comparator's merge operations are biased to prefer the data in the
450 // first profile parameter when the data is the same modulo case. We pass the
451 // incoming profile in this position to prefer accepting updates instead of
452 // preserving the original data. I.e., passing the incoming profile first
453 // accepts case changes, the other ways does not.
454 if (!comparator.MergeNames(profile, *this, &name) ||
455 !comparator.MergeEmailAddresses(profile, *this, &email) ||
456 !comparator.MergeCompanyNames(profile, *this, &company) ||
457 !comparator.MergePhoneNumbers(profile, *this, &phone_number) ||
458 !comparator.MergeAddresses(profile, *this, &address)) {
459 NOTREACHED();
486 return false; 460 return false;
487 } 461 }
488 462
489 l10n::CaseInsensitiveCompare compare;
490 AutofillType type = AutofillType(NAME_FULL);
491 base::string16 full_name = name_.GetInfo(type, app_locale);
492 // Always overwrite if the name parts are empty.
493 if (!name_.NamePartsAreEmpty() &&
494 compare.StringsEqual(full_name,
495 imported_name.GetInfo(type, app_locale))) {
496 // The imported name has the same full name string as the name for this
497 // profile. Because full names are _heuristically_ parsed into
498 // {first, middle, last} name components, it's possible that either the
499 // existing name or the imported name was misparsed. Prefer to keep the
500 // name whose {first, middle, last} components do not match those computed
501 // by the heuristic parse, as this more likely represents the correct,
502 // user-input parse of the name.
503 NameInfo heuristically_parsed_name;
504 heuristically_parsed_name.SetInfo(type, full_name, app_locale);
505 if (imported_name.ParsedNamesAreEqual(heuristically_parsed_name))
506 return false;
507 }
508
509 name_.OverwriteName(imported_name);
510 return true;
511 }
512
513 bool AutofillProfile::OverwriteWith(const AutofillProfile& profile,
514 const std::string& app_locale) {
515 // Verified profiles should never be overwritten with unverified data.
516 DCHECK(!IsVerified() || profile.IsVerified());
517 set_origin(profile.origin()); 463 set_origin(profile.origin());
518 set_language_code(profile.language_code()); 464 set_language_code(profile.language_code());
519 set_use_count(profile.use_count() + use_count()); 465 set_use_count(profile.use_count() + use_count());
520 if (profile.use_date() > use_date()) 466 if (profile.use_date() > use_date())
521 set_use_date(profile.use_date()); 467 set_use_date(profile.use_date());
522 468
523 // |types_to_overwrite| is initially populated with all types that have 469 // Now that the preferred values have been obtained, update the fields which
524 // non-empty data in the incoming |profile|. After adjustment, all data from 470 // need to be modified, if any. Note: that we're comparing the fields for
525 // |profile| corresponding to types in |types_to_overwrite| is overwritten in 471 // representational equality below (i.e., are the values byte for byte the
526 // |this| profile. 472 // same).
527 ServerFieldTypeSet types_to_overwrite;
528 profile.GetNonEmptyTypes(app_locale, &types_to_overwrite);
529 473
530 // Only transfer "full" types (e.g. full name) and not fragments (e.g. 474 bool modified = false;
531 // first name, last name).
532 CollapseCompoundFieldTypes(&types_to_overwrite);
533 475
534 // Remove ADDRESS_HOME_STREET_ADDRESS to ensure a merge of the address line by 476 if (name_ != name) {
535 // line. See comment below. 477 name_ = name;
536 types_to_overwrite.erase(ADDRESS_HOME_STREET_ADDRESS); 478 modified = true;
537
538 l10n::CaseInsensitiveCompare compare;
539
540 // Special case for addresses. With the whole address comparison, it is now
541 // necessary to make sure to keep the best address format: both lines used.
542 // This is because some sites might not have an address line 2 and the
543 // previous value should not be replaced with an empty string in that case.
544 if (compare.StringsEqual(
545 CanonicalizeProfileString(
546 profile.GetRawInfo(ADDRESS_HOME_STREET_ADDRESS)),
547 CanonicalizeProfileString(GetRawInfo(ADDRESS_HOME_STREET_ADDRESS))) &&
548 !GetRawInfo(ADDRESS_HOME_LINE2).empty() &&
549 profile.GetRawInfo(ADDRESS_HOME_LINE2).empty()) {
550 types_to_overwrite.erase(ADDRESS_HOME_LINE1);
551 types_to_overwrite.erase(ADDRESS_HOME_LINE2);
552 } 479 }
553 480
554 bool did_overwrite = false; 481 if (email_ != email) {
555 482 email_ = email;
556 for (const ServerFieldType field_type : types_to_overwrite) { 483 modified = true;
557 // Special case for names.
558 if (AutofillType(field_type).group() == NAME) {
559 did_overwrite |= OverwriteName(profile.name_, app_locale);
560 continue;
561 }
562
563 base::string16 new_value = profile.GetRawInfo(field_type);
564 // Overwrite the data in |this| profile for the field type and set
565 // |did_overwrite| if the previous data was different than the |new_value|.
566 if (GetRawInfo(field_type) != new_value) {
567 SetRawInfo(field_type, new_value);
568 did_overwrite = true;
569 }
570 } 484 }
571 485
572 return did_overwrite; 486 if (company_ != company) {
487 company_ = company;
488 modified = true;
489 }
490
491 if (phone_number_ != phone_number) {
492 phone_number_ = phone_number;
493 modified = true;
494 }
495
496 if (address_ != address) {
497 address_ = address;
498 modified = true;
499 }
500
501 return modified;
573 } 502 }
574 503
575 bool AutofillProfile::SaveAdditionalInfo(const AutofillProfile& profile, 504 bool AutofillProfile::SaveAdditionalInfo(const AutofillProfile& profile,
576 const std::string& app_locale) { 505 const std::string& app_locale) {
577 // If both profiles are verified, do not merge them. 506 // If both profiles are verified, do not merge them.
578 if (IsVerified() && profile.IsVerified()) 507 if (IsVerified() && profile.IsVerified())
579 return false; 508 return false;
580 509
581 ServerFieldTypeSet field_types, other_field_types; 510 AutofillProfileComparator comparator(app_locale);
582 GetNonEmptyTypes(app_locale, &field_types);
583 profile.GetNonEmptyTypes(app_locale, &other_field_types);
584 511
585 // The address needs to be compared line by line to take into account the 512 // SaveAdditionalInfo should not have been called if the profiles were not
586 // logic for empty fields implemented in the loop. 513 // already deemed to be mergeable.
587 field_types.erase(ADDRESS_HOME_STREET_ADDRESS); 514 DCHECK(comparator.AreMergeable(*this, profile));
588 l10n::CaseInsensitiveCompare compare;
589 for (ServerFieldType field_type : field_types) {
590 if (other_field_types.count(field_type)) {
591 AutofillType type = AutofillType(field_type);
592 // Special cases for name and phone. If the whole/full value matches, skip
593 // the individual fields comparison.
594 if (type.group() == NAME &&
595 compare.StringsEqual(
596 profile.GetInfo(AutofillType(NAME_FULL), app_locale),
597 GetInfo(AutofillType(NAME_FULL), app_locale))) {
598 continue;
599 }
600 if (type.group() == PHONE_HOME &&
601 i18n::PhoneNumbersMatch(
602 GetRawInfo(PHONE_HOME_WHOLE_NUMBER),
603 profile.GetRawInfo(PHONE_HOME_WHOLE_NUMBER),
604 base::UTF16ToASCII(GetRawInfo(ADDRESS_HOME_COUNTRY)),
605 app_locale)) {
606 continue;
607 }
608 515
609 // Special case for postal codes, where postal codes with/without spaces 516 // We don't replace verified profile data with unverified profile data. But,
610 // in them are considered equivalent. 517 // we can merge two verified profiles or merge verified profile data into an
611 if (field_type == ADDRESS_HOME_ZIP) { 518 // unverified profile.
612 base::string16 profile_zip;
613 base::string16 current_zip;
614 base::RemoveChars(profile.GetRawInfo(field_type), ASCIIToUTF16(" "),
615 &profile_zip);
616 base::RemoveChars(GetRawInfo(field_type), ASCIIToUTF16(" "),
617 &current_zip);
618 if (!compare.StringsEqual(profile_zip, current_zip))
619 return false;
620 continue;
621 }
622
623 // Special case for the address because the comparison uses canonicalized
624 // values. Start by comparing the address line by line. If it fails, make
625 // sure that the address as a whole is different before returning false.
626 // It is possible that the user put the info from line 2 on line 1 because
627 // of a certain form for example.
628 if (field_type == ADDRESS_HOME_LINE1 ||
629 field_type == ADDRESS_HOME_LINE2) {
630 if (!compare.StringsEqual(
631 CanonicalizeProfileString(profile.GetRawInfo(field_type)),
632 CanonicalizeProfileString(GetRawInfo(field_type))) &&
633 !compare.StringsEqual(CanonicalizeProfileString(profile.GetRawInfo(
634 ADDRESS_HOME_STREET_ADDRESS)),
635 CanonicalizeProfileString(GetRawInfo(
636 ADDRESS_HOME_STREET_ADDRESS)))) {
637 return false;
638 }
639 continue;
640 }
641
642 // Special case for the state to support abbreviations. Currently only the
643 // US states are supported.
644 if (field_type == ADDRESS_HOME_STATE) {
645 base::string16 full;
646 base::string16 abbreviation;
647 state_names::GetNameAndAbbreviation(GetRawInfo(ADDRESS_HOME_STATE),
648 &full, &abbreviation);
649 if (compare.StringsEqual(profile.GetRawInfo(ADDRESS_HOME_STATE),
650 full) ||
651 compare.StringsEqual(profile.GetRawInfo(ADDRESS_HOME_STATE),
652 abbreviation))
653 continue;
654 }
655
656 // Special case for company names to support cannonicalized variations.
657 if (field_type == COMPANY_NAME) {
658 if (compare.StringsEqual(
659 CanonicalizeProfileString(profile.GetRawInfo(field_type)),
660 CanonicalizeProfileString(GetRawInfo(field_type)))) {
661 continue;
662 }
663 }
664
665 // Special case for middle name to support initials.
666 if (field_type == NAME_MIDDLE) {
667 base::string16 middle_name = GetRawInfo(NAME_MIDDLE);
668 base::string16 profile_middle_name = profile.GetRawInfo(NAME_MIDDLE);
669 DCHECK(!middle_name.empty());
670 DCHECK(!profile_middle_name.empty());
671 // If one of the two middle names is an initial that matches the first
672 // letter of the other middle name, they are considered equivalent.
673 if ((middle_name.size() == 1 || profile_middle_name.size() == 1) &&
674 middle_name[0] == profile_middle_name[0]) {
675 continue;
676 }
677 }
678
679 if (!compare.StringsEqual(profile.GetRawInfo(field_type),
680 GetRawInfo(field_type))) {
681 return false;
682 }
683 }
684 }
685
686 if (!IsVerified() || profile.IsVerified()) { 519 if (!IsVerified() || profile.IsVerified()) {
687 if (OverwriteWith(profile, app_locale)) { 520 if (MergeDataFrom(profile, app_locale)) {
688 AutofillMetrics::LogProfileActionOnFormSubmitted( 521 AutofillMetrics::LogProfileActionOnFormSubmitted(
689 AutofillMetrics::EXISTING_PROFILE_UPDATED); 522 AutofillMetrics::EXISTING_PROFILE_UPDATED);
690 } else { 523 } else {
691 AutofillMetrics::LogProfileActionOnFormSubmitted( 524 AutofillMetrics::LogProfileActionOnFormSubmitted(
692 AutofillMetrics::EXISTING_PROFILE_USED); 525 AutofillMetrics::EXISTING_PROFILE_USED);
693 } 526 }
694 } 527 }
695 return true; 528 return true;
696 } 529 }
697 530
(...skipping 77 matching lines...) Expand 10 before | Expand all | Expand 10 after
775 contents_utf8.append(language_code()); 608 contents_utf8.append(language_code());
776 server_id_ = base::SHA1HashString(contents_utf8); 609 server_id_ = base::SHA1HashString(contents_utf8);
777 } 610 }
778 611
779 void AutofillProfile::RecordAndLogUse() { 612 void AutofillProfile::RecordAndLogUse() {
780 UMA_HISTOGRAM_COUNTS_1000("Autofill.DaysSinceLastUse.Profile", 613 UMA_HISTOGRAM_COUNTS_1000("Autofill.DaysSinceLastUse.Profile",
781 (base::Time::Now() - use_date()).InDays()); 614 (base::Time::Now() - use_date()).InDays());
782 RecordUse(); 615 RecordUse();
783 } 616 }
784 617
785 // static
786 base::string16 AutofillProfile::CanonicalizeProfileString(
787 const base::string16& str) {
788 // The locale doesn't matter for general string canonicalization.
789 AutofillProfileComparator comparator("en-US");
790 return comparator.NormalizeForComparison(str);
791 }
792
793 void AutofillProfile::GetSupportedTypes( 618 void AutofillProfile::GetSupportedTypes(
794 ServerFieldTypeSet* supported_types) const { 619 ServerFieldTypeSet* supported_types) const {
795 FormGroupList info = FormGroups(); 620 FormGroupList info = FormGroups();
796 for (const auto& it : info) { 621 for (const auto& it : info) {
797 it->GetSupportedTypes(supported_types); 622 it->GetSupportedTypes(supported_types);
798 } 623 }
799 } 624 }
800 625
801 base::string16 AutofillProfile::ConstructInferredLabel( 626 base::string16 AutofillProfile::ConstructInferredLabel(
802 const std::vector<ServerFieldType>& included_fields, 627 const std::vector<ServerFieldType>& included_fields,
(...skipping 216 matching lines...) Expand 10 before | Expand all | Expand 10 after
1019 << " " << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_CITY)) << " " 844 << " " << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_CITY)) << " "
1020 << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_STATE)) << " " 845 << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_STATE)) << " "
1021 << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_ZIP)) << " " 846 << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_ZIP)) << " "
1022 << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_SORTING_CODE)) << " " 847 << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_SORTING_CODE)) << " "
1023 << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_COUNTRY)) << " " 848 << UTF16ToUTF8(profile.GetRawInfo(ADDRESS_HOME_COUNTRY)) << " "
1024 << profile.language_code() << " " 849 << profile.language_code() << " "
1025 << UTF16ToUTF8(profile.GetRawInfo(PHONE_HOME_WHOLE_NUMBER)); 850 << UTF16ToUTF8(profile.GetRawInfo(PHONE_HOME_WHOLE_NUMBER));
1026 } 851 }
1027 852
1028 } // namespace autofill 853 } // namespace autofill
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698