Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2012 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 "chrome/browser/ui/autofill/autofill_dialog_controller_impl.h" | 5 #include "chrome/browser/ui/autofill/autofill_dialog_controller_impl.h" |
| 6 | 6 |
| 7 #include <string> | 7 #include <string> |
| 8 | 8 |
| 9 #include "base/logging.h" | 9 #include "base/logging.h" |
| 10 #include "base/string_number_conversions.h" | |
| 10 #include "base/string_split.h" | 11 #include "base/string_split.h" |
| 11 #include "base/string_util.h" | 12 #include "base/string_util.h" |
| 12 #include "base/utf_string_conversions.h" | 13 #include "base/utf_string_conversions.h" |
| 13 #include "chrome/browser/autofill/autofill_country.h" | 14 #include "chrome/browser/autofill/autofill_country.h" |
| 14 #include "chrome/browser/autofill/autofill_manager.h" | 15 #include "chrome/browser/autofill/autofill_manager.h" |
| 15 #include "chrome/browser/autofill/autofill_type.h" | 16 #include "chrome/browser/autofill/autofill_type.h" |
| 16 #include "chrome/browser/autofill/personal_data_manager.h" | 17 #include "chrome/browser/autofill/personal_data_manager.h" |
| 17 #include "chrome/browser/autofill/personal_data_manager_factory.h" | 18 #include "chrome/browser/autofill/personal_data_manager_factory.h" |
| 18 #include "chrome/browser/autofill/validation.h" | 19 #include "chrome/browser/autofill/validation.h" |
| 19 #include "chrome/browser/autofill/wallet/full_wallet.h" | 20 #include "chrome/browser/autofill/wallet/full_wallet.h" |
| 20 #include "chrome/browser/autofill/wallet/wallet_items.h" | 21 #include "chrome/browser/autofill/wallet/wallet_items.h" |
| 21 #include "chrome/browser/autofill/wallet/wallet_service_url.h" | 22 #include "chrome/browser/autofill/wallet/wallet_service_url.h" |
| 22 #include "chrome/browser/profiles/profile.h" | 23 #include "chrome/browser/profiles/profile.h" |
| 23 #include "chrome/browser/ui/autofill/autofill_dialog_view.h" | 24 #include "chrome/browser/ui/autofill/autofill_dialog_view.h" |
| 25 #include "chrome/browser/ui/autofill/data_model_wrapper.h" | |
| 24 #include "chrome/common/form_data.h" | 26 #include "chrome/common/form_data.h" |
| 25 #include "content/public/browser/navigation_controller.h" | 27 #include "content/public/browser/navigation_controller.h" |
| 26 #include "content/public/browser/navigation_details.h" | 28 #include "content/public/browser/navigation_details.h" |
| 27 #include "content/public/browser/navigation_entry.h" | 29 #include "content/public/browser/navigation_entry.h" |
| 28 #include "content/public/browser/notification_service.h" | 30 #include "content/public/browser/notification_service.h" |
| 29 #include "content/public/browser/notification_types.h" | 31 #include "content/public/browser/notification_types.h" |
| 30 #include "content/public/browser/web_contents.h" | 32 #include "content/public/browser/web_contents.h" |
| 31 #include "content/public/common/url_constants.h" | 33 #include "content/public/common/url_constants.h" |
| 32 #include "grit/chromium_strings.h" | 34 #include "grit/chromium_strings.h" |
| 33 #include "grit/generated_resources.h" | 35 #include "grit/generated_resources.h" |
| (...skipping 337 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 371 return string16(); | 373 return string16(); |
| 372 | 374 |
| 373 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | 375 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); |
| 374 std::string item_key = model->GetItemKeyAt(model->checked_item()); | 376 std::string item_key = model->GetItemKeyAt(model->checked_item()); |
| 375 if (item_key.empty()) | 377 if (item_key.empty()) |
| 376 return string16(); | 378 return string16(); |
| 377 | 379 |
| 378 if (section == SECTION_EMAIL) | 380 if (section == SECTION_EMAIL) |
| 379 return model->GetLabelAt(model->checked_item()); | 381 return model->GetLabelAt(model->checked_item()); |
| 380 | 382 |
| 381 if (section == SECTION_CC) { | 383 scoped_ptr<DataModelWrapper> wrapper = CreateWrapper(section); |
| 384 return wrapper->GetDisplayText(); | |
| 385 } | |
| 386 | |
| 387 scoped_ptr<DataModelWrapper> AutofillDialogControllerImpl::CreateWrapper( | |
| 388 DialogSection section) { | |
| 389 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | |
| 390 std::string item_key = model->GetItemKeyAt(model->checked_item()); | |
| 391 scoped_ptr<DataModelWrapper> wrapper; | |
| 392 if (item_key.empty()) | |
| 393 return wrapper.Pass(); | |
| 394 | |
| 395 if (CanPayWithWallet()) { | |
| 396 int index; | |
| 397 bool success = !base::StringToInt(item_key, &index); | |
|
Dan Beam
2013/02/15 02:11:45
shouldn't this be without the "!" ?
Evan Stade
2013/02/20 00:03:39
yes
| |
| 398 DCHECK(success); | |
| 399 | |
| 400 if (section == SECTION_CC) { | |
| 401 wrapper.reset( | |
| 402 new WalletInstrumentWrapper(wallet_items_->instruments()[index])); | |
| 403 } else { | |
| 404 wrapper.reset( | |
| 405 new WalletAddressWrapper(wallet_items_->addresses()[index])); | |
| 406 } | |
| 407 } else if (section == SECTION_CC) { | |
| 382 CreditCard* card = GetManager()->GetCreditCardByGUID(item_key); | 408 CreditCard* card = GetManager()->GetCreditCardByGUID(item_key); |
| 383 return card->TypeAndLastFourDigits(); | 409 DCHECK(card); |
| 410 wrapper.reset(new AutofillCreditCardWrapper(card)); | |
| 411 } else { | |
| 412 // Calculate the variant by looking at how many items come from the same | |
| 413 // FormGroup. TODO(estade): add a test for this. | |
| 414 size_t variant = 0; | |
| 415 for (int i = model->checked_item() - 1; i >= 0; --i) { | |
| 416 if (model->GetItemKeyAt(i) == item_key) | |
| 417 variant++; | |
| 418 else | |
| 419 break; | |
| 420 } | |
| 421 | |
| 422 AutofillProfile* profile = GetManager()->GetProfileByGUID(item_key); | |
| 423 DCHECK(profile); | |
| 424 wrapper.reset(new AutofillDataModelWrapper(profile, variant)); | |
| 384 } | 425 } |
| 385 | 426 |
| 386 const std::string app_locale = AutofillCountry::ApplicationLocale(); | 427 return wrapper.Pass(); |
| 387 AutofillProfile* profile = GetManager()->GetProfileByGUID(item_key); | |
| 388 string16 comma = ASCIIToUTF16(", "); | |
| 389 string16 label = profile->GetInfo(NAME_FULL, app_locale) + | |
| 390 comma + profile->GetInfo(ADDRESS_HOME_LINE1, app_locale); | |
| 391 string16 address2 = profile->GetInfo(ADDRESS_HOME_LINE2, app_locale); | |
| 392 if (!address2.empty()) | |
| 393 label += comma + address2; | |
| 394 label += ASCIIToUTF16("\n") + | |
| 395 profile->GetInfo(ADDRESS_HOME_CITY, app_locale) + comma + | |
| 396 profile->GetInfo(ADDRESS_HOME_STATE, app_locale) + ASCIIToUTF16(" ") + | |
| 397 profile->GetInfo(ADDRESS_HOME_ZIP, app_locale); | |
| 398 return label; | |
| 399 } | 428 } |
| 400 | 429 |
| 401 gfx::Image AutofillDialogControllerImpl::SuggestionIconForSection( | 430 gfx::Image AutofillDialogControllerImpl::SuggestionIconForSection( |
| 402 DialogSection section) { | 431 DialogSection section) { |
| 403 if (section != SECTION_CC) | 432 if (section != SECTION_CC) |
| 404 return gfx::Image(); | 433 return gfx::Image(); |
| 405 | 434 |
| 406 std::string item_key = | 435 scoped_ptr<DataModelWrapper> model = CreateWrapper(section); |
| 407 suggested_cc_.GetItemKeyAt(suggested_cc_.checked_item()); | 436 if (!model.get()) |
| 408 if (item_key.empty()) | |
| 409 return gfx::Image(); | 437 return gfx::Image(); |
| 410 ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance(); | 438 |
| 411 CreditCard* card = GetManager()->GetCreditCardByGUID(item_key); | 439 return model->GetIcon(); |
| 412 return rb.GetImageNamed(card->IconResourceId()); | |
| 413 } | 440 } |
| 414 | 441 |
| 415 void AutofillDialogControllerImpl::EditClickedForSection( | 442 void AutofillDialogControllerImpl::EditClickedForSection( |
| 416 DialogSection section) { | 443 DialogSection section) { |
| 417 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | 444 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); |
| 418 DetailInputs* inputs = MutableRequestedFieldsForSection(section); | 445 DetailInputs* inputs = MutableRequestedFieldsForSection(section); |
| 419 | 446 |
| 420 if (section == SECTION_EMAIL) { | 447 if (section == SECTION_EMAIL) { |
| 421 // TODO(estade): shouldn't need to make this check. | 448 // TODO(estade): shouldn't need to make this check. |
| 422 if (inputs->empty()) | 449 if (inputs->empty()) |
| 423 return; | 450 return; |
| 424 | 451 |
| 425 (*inputs)[0].autofilled_value = model->GetLabelAt(model->checked_item()); | 452 (*inputs)[0].autofilled_value = model->GetLabelAt(model->checked_item()); |
| 426 } else { | 453 } else { |
| 427 std::string guid = model->GetItemKeyAt(model->checked_item()); | 454 scoped_ptr<DataModelWrapper> model = CreateWrapper(section); |
| 428 DCHECK(!guid.empty()); | 455 model->FillInputs(inputs); |
| 429 | |
| 430 FormGroup* form_group = section == SECTION_CC ? | |
| 431 static_cast<FormGroup*>(GetManager()->GetCreditCardByGUID(guid)) : | |
| 432 static_cast<FormGroup*>(GetManager()->GetProfileByGUID(guid)); | |
| 433 DCHECK(form_group); | |
| 434 FillInputFromFormGroup(form_group, inputs); | |
| 435 } | 456 } |
| 436 | 457 |
| 437 section_editing_state_[section] = true; | 458 section_editing_state_[section] = true; |
| 438 view_->UpdateSection(section); | 459 view_->UpdateSection(section); |
| 439 } | 460 } |
| 440 | 461 |
| 441 bool AutofillDialogControllerImpl::InputIsValid(AutofillFieldType type, | 462 bool AutofillDialogControllerImpl::InputIsValid(AutofillFieldType type, |
| 442 const string16& value) { | 463 const string16& value) { |
| 443 switch (type) { | 464 switch (type) { |
| 444 case EMAIL_ADDRESS: | 465 case EMAIL_ADDRESS: |
| (...skipping 268 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 713 WalletRequestCompleted(true); | 734 WalletRequestCompleted(true); |
| 714 } | 735 } |
| 715 | 736 |
| 716 void AutofillDialogControllerImpl::OnDidGetWalletItems( | 737 void AutofillDialogControllerImpl::OnDidGetWalletItems( |
| 717 scoped_ptr<wallet::WalletItems> wallet_items) { | 738 scoped_ptr<wallet::WalletItems> wallet_items) { |
| 718 bool items_changed = !wallet_items_ || *wallet_items != *wallet_items_; | 739 bool items_changed = !wallet_items_ || *wallet_items != *wallet_items_; |
| 719 wallet_items_ = wallet_items.Pass(); | 740 wallet_items_ = wallet_items.Pass(); |
| 720 WalletRequestCompleted(true); | 741 WalletRequestCompleted(true); |
| 721 | 742 |
| 722 if (items_changed) { | 743 if (items_changed) { |
| 744 GenerateSuggestionsModels(); | |
| 745 view_->ModelChanged(); | |
| 723 view_->UpdateAccountChooser(); | 746 view_->UpdateAccountChooser(); |
| 724 view_->UpdateNotificationArea(); | 747 view_->UpdateNotificationArea(); |
| 725 } | 748 } |
| 726 } | 749 } |
| 727 | 750 |
| 728 void AutofillDialogControllerImpl::OnDidSaveAddress( | 751 void AutofillDialogControllerImpl::OnDidSaveAddress( |
| 729 const std::string& address_id) { | 752 const std::string& address_id) { |
| 730 NOTIMPLEMENTED() << " address_id=" << address_id; | 753 NOTIMPLEMENTED() << " address_id=" << address_id; |
| 731 WalletRequestCompleted(true); | 754 WalletRequestCompleted(true); |
| 732 } | 755 } |
| (...skipping 90 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 823 } | 846 } |
| 824 | 847 |
| 825 wallet_client_.GetWalletItems(this); | 848 wallet_client_.GetWalletItems(this); |
| 826 refresh_wallet_items_queued_ = false; | 849 refresh_wallet_items_queued_ = false; |
| 827 } | 850 } |
| 828 | 851 |
| 829 void AutofillDialogControllerImpl::WalletRequestCompleted(bool success) { | 852 void AutofillDialogControllerImpl::WalletRequestCompleted(bool success) { |
| 830 if (!success) { | 853 if (!success) { |
| 831 had_wallet_error_ = true; | 854 had_wallet_error_ = true; |
| 832 wallet_items_.reset(); | 855 wallet_items_.reset(); |
| 856 GenerateSuggestionsModels(); | |
| 857 view_->ModelChanged(); | |
| 833 view_->UpdateAccountChooser(); | 858 view_->UpdateAccountChooser(); |
| 834 view_->UpdateNotificationArea(); | 859 view_->UpdateNotificationArea(); |
| 835 return; | 860 return; |
| 836 } | 861 } |
| 837 | 862 |
| 838 if (refresh_wallet_items_queued_) | 863 if (refresh_wallet_items_queued_) |
| 839 ScheduleRefreshWalletItems(); | 864 ScheduleRefreshWalletItems(); |
| 840 } | 865 } |
| 841 | 866 |
| 842 void AutofillDialogControllerImpl::GenerateSuggestionsModels() { | 867 void AutofillDialogControllerImpl::GenerateSuggestionsModels() { |
| 843 suggested_cc_.Reset(); | 868 suggested_cc_.Reset(); |
| 844 suggested_billing_.Reset(); | 869 suggested_billing_.Reset(); |
| 845 suggested_email_.Reset(); | 870 suggested_email_.Reset(); |
| 846 suggested_shipping_.Reset(); | 871 suggested_shipping_.Reset(); |
| 847 | 872 |
| 848 PersonalDataManager* manager = GetManager(); | 873 if (CanPayWithWallet()) { |
| 849 const std::vector<CreditCard*>& cards = manager->credit_cards(); | 874 if (wallet_items_.get()) { |
| 850 for (size_t i = 0; i < cards.size(); ++i) { | 875 // TODO(estade): seems we need to hardcode the email address. |
| 851 suggested_cc_.AddKeyedItem(cards[i]->guid(), cards[i]->Label()); | 876 // TODO(estade): CC and billing need to be combined into one section, |
| 852 } | 877 // and suggestions added here. |
| 853 | 878 const std::vector<wallet::Address*>& addresses = |
| 854 const std::vector<AutofillProfile*>& profiles = manager->GetProfiles(); | 879 wallet_items_->addresses(); |
| 855 const std::string app_locale = AutofillCountry::ApplicationLocale(); | 880 for (size_t i = 0; i < addresses.size(); ++i) { |
| 856 for (size_t i = 0; i < profiles.size(); ++i) { | 881 suggested_billing_.AddKeyedItem(base::IntToString(i), |
| 857 if (!IsCompleteProfile(*profiles[i])) | 882 addresses[i]->DisplayName()); |
| 858 continue; | 883 suggested_shipping_.AddKeyedItem(base::IntToString(i), |
| 859 | 884 addresses[i]->DisplayName()); |
| 860 // Add all email addresses. | 885 } |
| 861 std::vector<string16> values; | 886 } |
| 862 profiles[i]->GetMultiInfo(EMAIL_ADDRESS, app_locale, &values); | 887 } else { |
| 863 for (size_t j = 0; j < values.size(); ++j) { | 888 PersonalDataManager* manager = GetManager(); |
| 864 if (!values[j].empty()) | 889 const std::vector<CreditCard*>& cards = manager->credit_cards(); |
| 865 suggested_email_.AddKeyedItem(profiles[i]->guid(), values[j]); | 890 for (size_t i = 0; i < cards.size(); ++i) { |
| 891 suggested_cc_.AddKeyedItem(cards[i]->guid(), cards[i]->Label()); | |
| 866 } | 892 } |
| 867 | 893 |
| 868 // Don't add variants for addresses: the email variants are handled above, | 894 const std::vector<AutofillProfile*>& profiles = manager->GetProfiles(); |
| 869 // name is part of credit card and we'll just ignore phone number variants. | 895 const std::string app_locale = AutofillCountry::ApplicationLocale(); |
| 870 suggested_billing_.AddKeyedItem(profiles[i]->guid(), profiles[i]->Label()); | 896 for (size_t i = 0; i < profiles.size(); ++i) { |
| 871 suggested_shipping_.AddKeyedItem(profiles[i]->guid(), profiles[i]->Label()); | 897 if (!IsCompleteProfile(*profiles[i])) |
| 898 continue; | |
| 899 | |
| 900 // Add all email addresses. | |
| 901 std::vector<string16> values; | |
| 902 profiles[i]->GetMultiInfo(EMAIL_ADDRESS, app_locale, &values); | |
| 903 for (size_t j = 0; j < values.size(); ++j) { | |
| 904 if (!values[j].empty()) | |
| 905 suggested_email_.AddKeyedItem(profiles[i]->guid(), values[j]); | |
| 906 } | |
| 907 | |
| 908 // Don't add variants for addresses: the email variants are handled above, | |
| 909 // name is part of credit card and we'll just ignore phone number | |
| 910 // variants. | |
| 911 suggested_billing_.AddKeyedItem(profiles[i]->guid(), | |
| 912 profiles[i]->Label()); | |
| 913 suggested_shipping_.AddKeyedItem(profiles[i]->guid(), | |
| 914 profiles[i]->Label()); | |
| 915 } | |
| 872 } | 916 } |
| 873 | 917 |
| 874 suggested_email_.AddKeyedItem( | 918 suggested_email_.AddKeyedItem( |
| 875 std::string(), | 919 std::string(), |
| 876 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_EMAIL_ADDRESS)); | 920 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_EMAIL_ADDRESS)); |
| 877 suggested_cc_.AddKeyedItem( | 921 suggested_cc_.AddKeyedItem( |
| 878 std::string(), | 922 std::string(), |
| 879 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_CREDIT_CARD)); | 923 l10n_util::GetStringUTF16(IDS_AUTOFILL_DIALOG_ADD_CREDIT_CARD)); |
| 880 suggested_billing_.AddKeyedItem( | 924 suggested_billing_.AddKeyedItem( |
| 881 std::string(), | 925 std::string(), |
| (...skipping 13 matching lines...) Expand all Loading... | |
| 895 } | 939 } |
| 896 } | 940 } |
| 897 | 941 |
| 898 return true; | 942 return true; |
| 899 } | 943 } |
| 900 | 944 |
| 901 void AutofillDialogControllerImpl::FillOutputForSectionWithComparator( | 945 void AutofillDialogControllerImpl::FillOutputForSectionWithComparator( |
| 902 DialogSection section, | 946 DialogSection section, |
| 903 const InputFieldComparator& compare) { | 947 const InputFieldComparator& compare) { |
| 904 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); | 948 SuggestionsMenuModel* model = SuggestionsMenuModelForSection(section); |
| 905 std::string guid = model->GetItemKeyAt(model->checked_item()); | 949 std::string item_key = model->GetItemKeyAt(model->checked_item()); |
| 906 PersonalDataManager* manager = GetManager(); | 950 if (!item_key.empty() && !section_editing_state_[section]) { |
| 907 if (!guid.empty() && !section_editing_state_[section]) { | 951 scoped_ptr<DataModelWrapper> model = CreateWrapper(section); |
| 908 FormGroup* form_group = section == SECTION_CC ? | 952 // Only fill in data that is associated with this section. |
| 909 static_cast<FormGroup*>(manager->GetCreditCardByGUID(guid)) : | 953 const DetailInputs& inputs = RequestedFieldsForSection(section); |
| 910 static_cast<FormGroup*>(manager->GetProfileByGUID(guid)); | 954 model->FillFormStructure(inputs, compare, &form_structure_); |
| 911 DCHECK(form_group); | |
| 912 | |
| 913 // Calculate the variant by looking at how many items come from the same | |
| 914 // FormGroup. TODO(estade): add a test for this. | |
| 915 size_t variant = 0; | |
| 916 for (int i = model->checked_item() - 1; i >= 0; --i) { | |
| 917 if (model->GetItemKeyAt(i) == guid) | |
| 918 variant++; | |
| 919 else | |
| 920 break; | |
| 921 } | |
| 922 | |
| 923 FillFormStructureForSection(*form_group, variant, section, compare); | |
| 924 | 955 |
| 925 // CVC needs special-casing because the CreditCard class doesn't store | 956 // CVC needs special-casing because the CreditCard class doesn't store |
| 926 // or handle them. | 957 // or handle them. |
| 927 if (section == SECTION_CC) | 958 if (section == SECTION_CC) |
| 928 SetCvcResult(view_->GetCvc()); | 959 SetCvcResult(view_->GetCvc()); |
| 929 } else { | 960 } else { |
| 930 // The user manually input data. | 961 // The user manually input data. |
| 962 PersonalDataManager* manager = GetManager(); | |
| 931 DetailOutputMap output; | 963 DetailOutputMap output; |
| 932 view_->GetUserInput(section, &output); | 964 view_->GetUserInput(section, &output); |
| 933 | 965 |
| 934 // Save the info as new or edited data, then fill it into |form_structure_|. | 966 // Save the info as new or edited data, then fill it into |form_structure_|. |
| 935 if (section == SECTION_CC) { | 967 if (section == SECTION_CC) { |
| 936 CreditCard card; | 968 CreditCard card; |
| 937 FillFormGroupFromOutputs(output, &card); | 969 FillFormGroupFromOutputs(output, &card); |
| 938 if (view_->SaveDetailsLocally()) | 970 if (view_->SaveDetailsLocally()) |
| 939 manager->SaveImportedCreditCard(card); | 971 manager->SaveImportedCreditCard(card); |
| 940 FillFormStructureForSection(card, 0, section, compare); | 972 FillFormStructureForSection(card, 0, section, compare); |
| (...skipping 92 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1033 DialogSection section) { | 1065 DialogSection section) { |
| 1034 return const_cast<DetailInputs*>(&RequestedFieldsForSection(section)); | 1066 return const_cast<DetailInputs*>(&RequestedFieldsForSection(section)); |
| 1035 } | 1067 } |
| 1036 | 1068 |
| 1037 void AutofillDialogControllerImpl::HidePopup() { | 1069 void AutofillDialogControllerImpl::HidePopup() { |
| 1038 if (popup_controller_) | 1070 if (popup_controller_) |
| 1039 popup_controller_->Hide(); | 1071 popup_controller_->Hide(); |
| 1040 } | 1072 } |
| 1041 | 1073 |
| 1042 } // namespace autofill | 1074 } // namespace autofill |
| OLD | NEW |