diff options
Diffstat (limited to 'chrome/browser/autofill')
-rw-r--r-- | chrome/browser/autofill/autofill_dialog.cc | 4 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_dialog.h | 15 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_dialog_gtk.cc | 172 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_manager.cc | 37 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_manager.h | 14 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_profile.cc | 39 | ||||
-rw-r--r-- | chrome/browser/autofill/autofill_profile.h | 4 | ||||
-rw-r--r-- | chrome/browser/autofill/personal_data_manager.cc | 150 | ||||
-rw-r--r-- | chrome/browser/autofill/personal_data_manager.h | 76 | ||||
-rw-r--r-- | chrome/browser/autofill/personal_data_manager_unittest.cc | 176 |
10 files changed, 625 insertions, 62 deletions
diff --git a/chrome/browser/autofill/autofill_dialog.cc b/chrome/browser/autofill/autofill_dialog.cc index d15cca4..f574987 100644 --- a/chrome/browser/autofill/autofill_dialog.cc +++ b/chrome/browser/autofill/autofill_dialog.cc @@ -8,7 +8,7 @@ // function. The last one to implement the function should remove this file. #if defined(OS_WIN) || defined(OS_MACOSX) void ShowAutoFillDialog(AutoFillDialogObserver* observer, - const std::vector<AutoFillProfile>& profiles, - const std::vector<CreditCard>& credit_cards) { + const std::vector<AutoFillProfile*>& profiles, + const std::vector<CreditCard*>& credit_cards) { } #endif // defined(OS_WIN) || defined(OS_MACOSX) diff --git a/chrome/browser/autofill/autofill_dialog.h b/chrome/browser/autofill/autofill_dialog.h index 34006b4..7a5c966 100644 --- a/chrome/browser/autofill/autofill_dialog.h +++ b/chrome/browser/autofill/autofill_dialog.h @@ -14,10 +14,11 @@ // the user has applied changes to the AutoFill profile data. class AutoFillDialogObserver { public: - // The user has confirmed changes by clicking "Apply" or "OK". + // The user has confirmed changes by clicking "Apply" or "OK". Any of the + // parameters may be NULL. virtual void OnAutoFillDialogApply( - const std::vector<AutoFillProfile>& profiles, - const std::vector<CreditCard>& credit_cards) = 0; + std::vector<AutoFillProfile>* profiles, + std::vector<CreditCard>* credit_cards) = 0; protected: virtual ~AutoFillDialogObserver() {} @@ -29,8 +30,12 @@ class AutoFillDialogObserver { // changes made to the profile information through the dialog should be // transferred back into |profiles| and |credit_cards|. |observer| will be // notified by OnAutoFillDialogAccept when the user has applied changes. +// +// The PersonalDataManager owns the contents of these vectors. The lifetime of +// the contents is until the PersonalDataManager replaces them with new data +// whenever the web database is updated. void ShowAutoFillDialog(AutoFillDialogObserver* observer, - const std::vector<AutoFillProfile>& profiles, - const std::vector<CreditCard>& credit_cards); + const std::vector<AutoFillProfile*>& profiles, + const std::vector<CreditCard*>& credit_cards); #endif // CHROME_BROWSER_AUTOFILL_AUTOFILL_DIALOG_H_ diff --git a/chrome/browser/autofill/autofill_dialog_gtk.cc b/chrome/browser/autofill/autofill_dialog_gtk.cc index 6432f59..33a30a5 100644 --- a/chrome/browser/autofill/autofill_dialog_gtk.cc +++ b/chrome/browser/autofill/autofill_dialog_gtk.cc @@ -26,6 +26,10 @@ namespace { // Style for dialog group titles. const char kDialogGroupTitleMarkup[] = "<span weight='bold'>%s</span>"; +// The name of the object property used to store an entry widget pointer on +// another widget. +const char kButtonDataKey[] = "label-entry"; + // How far we indent dialog widgets, in pixels. const int kAutoFillDialogIndent = 5; @@ -73,6 +77,23 @@ void SetWhiteBackground(GtkWidget* widget) { gtk_widget_destroy(entry); } +string16 GetEntryText(GtkWidget* entry) { + return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(entry))); +} + +void SetEntryText(GtkWidget* entry, const string16& text) { + gtk_entry_set_text(GTK_ENTRY(entry), UTF16ToUTF8(text).c_str()); +} + +void SetButtonData(GtkWidget* widget, GtkWidget* entry) { + g_object_set_data(G_OBJECT(widget), kButtonDataKey, entry); +} + +GtkWidget* GetButtonData(GtkWidget* widget) { + return static_cast<GtkWidget*>( + g_object_get_data(G_OBJECT(widget), kButtonDataKey)); +} + //////////////////////////////////////////////////////////////////////////////// // Form Table helpers. // @@ -191,8 +212,8 @@ GtkWidget* FormTableAddLabelEntry( class AutoFillDialog { public: AutoFillDialog(AutoFillDialogObserver* observer, - const std::vector<AutoFillProfile>& profiles, - const std::vector<CreditCard>& credit_cards); + const std::vector<AutoFillProfile*>& profiles, + const std::vector<CreditCard*>& credit_cards); ~AutoFillDialog() {} // Shows the AutoFill dialog. @@ -216,6 +237,13 @@ class AutoFillDialog { // 'clicked' signal handler. We add a new credit card. static void OnAddCreditCardClicked(GtkButton* button, AutoFillDialog* dialog); + // 'clicked' signal handler. We delete the associated address. + static void OnDeleteAddressClicked(GtkButton* button, AutoFillDialog* dialog); + + // 'clicked' signal handler. We delete the associated credit card. + static void OnDeleteCreditCardClicked(GtkButton* button, + AutoFillDialog* dialog); + // 'changed' signal handler. We update the title of the expander widget with // the contents of the label entry widget. static void OnLabelChanged(GtkEntry* label, GtkWidget* expander); @@ -236,10 +264,14 @@ class AutoFillDialog { // Returns a GtkExpander that is added to the appropriate vbox. Each method // adds the necessary widgets and layout required to fill out information - // for either an address or a credit card. - GtkWidget* AddNewAddress(); + // for either an address or a credit card. The expander will be expanded by + // default if |expand| is true. + GtkWidget* AddNewAddress(bool expand); GtkWidget* AddNewCreditCard(); + // Adds a new address filled out with information from |profile|. + void AddAddress(const AutoFillProfile& profile); + // The list of current AutoFill profiles. std::vector<AutoFillProfile> profiles_; @@ -268,13 +300,18 @@ class AutoFillDialog { static AutoFillDialog* dialog = NULL; AutoFillDialog::AutoFillDialog(AutoFillDialogObserver* observer, - const std::vector<AutoFillProfile>& profiles, - const std::vector<CreditCard>& credit_cards) - : profiles_(profiles), - credit_cards_(credit_cards), - observer_(observer) { + const std::vector<AutoFillProfile*>& profiles, + const std::vector<CreditCard*>& credit_cards) + : observer_(observer) { DCHECK(observer); + // Copy the profiles. + std::vector<AutoFillProfile*>::const_iterator profile; + for (profile = profiles.begin(); profile != profiles.end(); ++profile) + profiles_.push_back(**profile); + + // TODO(jhawkins): Copy the credit cards. + dialog_ = gtk_dialog_new_with_buttons( l10n_util::GetStringUTF8(IDS_AUTOFILL_DIALOG_TITLE).c_str(), // AutoFill dialog is shared between all browser windows. @@ -328,7 +365,9 @@ AutoFillDialog::AutoFillDialog(AutoFillDialogObserver* observer, G_CALLBACK(OnAddAddressClicked)); gtk_box_pack_start_defaults(GTK_BOX(outer_vbox), addresses_vbox_); - // TODO(jhawkins): Add addresses from |profiles|. + std::vector<AutoFillProfile>::const_iterator iter; + for (iter = profiles_.begin(); iter != profiles_.end(); ++iter) + AddAddress(*iter); creditcards_vbox_ = InitGroup(IDS_AUTOFILL_CREDITCARDS_GROUP_NAME, IDS_AUTOFILL_ADD_CREDITCARD_BUTTON, @@ -352,10 +391,6 @@ void AutoFillDialog::OnDestroy(GtkWidget* widget, MessageLoop::current()->DeleteSoon(FROM_HERE, autofill_dialog); } -static string16 GetEntryText(GtkWidget* entry) { - return UTF8ToUTF16(gtk_entry_get_text(GTK_ENTRY(entry))); -} - static AutoFillProfile AutoFillProfileFromWidgetValues( const AddressWidgets& widgets) { // TODO(jhawkins): unique id? @@ -411,7 +446,8 @@ void AutoFillDialog::OnResponse(GtkDialog* dialog, gint response_id, } autofill_dialog->observer_->OnAutoFillDialogApply( - autofill_dialog->profiles_, autofill_dialog->credit_cards_); + &autofill_dialog->profiles_, + &autofill_dialog->credit_cards_); } if (response_id == GTK_RESPONSE_OK || response_id == GTK_RESPONSE_CANCEL) { @@ -422,7 +458,7 @@ void AutoFillDialog::OnResponse(GtkDialog* dialog, gint response_id, // static void AutoFillDialog::OnAddAddressClicked(GtkButton* button, AutoFillDialog* dialog) { - GtkWidget* new_address = dialog->AddNewAddress(); + GtkWidget* new_address = dialog->AddNewAddress(true); gtk_box_pack_start(GTK_BOX(dialog->addresses_vbox_), new_address, FALSE, FALSE, 0); gtk_widget_show_all(new_address); @@ -438,6 +474,50 @@ void AutoFillDialog::OnAddCreditCardClicked(GtkButton* button, } // static +void AutoFillDialog::OnDeleteAddressClicked(GtkButton* button, + AutoFillDialog* dialog) { + GtkWidget* entry = GetButtonData(GTK_WIDGET(button)); + string16 label = GetEntryText(entry); + + // TODO(jhawkins): Base this on ID. + + // Remove the profile. + for (std::vector<AutoFillProfile>::iterator iter = dialog->profiles_.begin(); + iter != dialog->profiles_.end(); + ++iter) { + if (iter->Label() == label) { + dialog->profiles_.erase(iter); + break; + } + } + + // Remove the set of address widgets. + for (std::vector<AddressWidgets>::iterator iter = + dialog->address_widgets_.begin(); + iter != dialog->address_widgets_.end(); + ++iter) { + if (iter->label == entry) { + dialog->address_widgets_.erase(iter); + break; + } + } + + // Get back to the expander widget. + GtkWidget* expander = gtk_widget_get_ancestor(GTK_WIDGET(button), + GTK_TYPE_EXPANDER); + DCHECK(expander); + + // Destroying the widget will also remove it from the parent container. + gtk_widget_destroy(expander); +} + +// static +void AutoFillDialog::OnDeleteCreditCardClicked(GtkButton* button, + AutoFillDialog* dialog) { + // TODO(jhawkins): Remove the associated credit card. +} + +// static void AutoFillDialog::OnLabelChanged(GtkEntry* label, GtkWidget* expander) { gtk_expander_set_label(GTK_EXPANDER(expander), gtk_entry_get_text(label)); } @@ -492,18 +572,17 @@ GtkWidget* AutoFillDialog::InitGroupContentArea(int name_id, gtk_container_add(GTK_CONTAINER(vbox_alignment), vbox); gtk_container_add(GTK_CONTAINER(frame), vbox_alignment); - // Make it expand by default. - gtk_expander_set_expanded(GTK_EXPANDER(expander), true); - *content_vbox = vbox; return expander; } -GtkWidget* AutoFillDialog::AddNewAddress() { +GtkWidget* AutoFillDialog::AddNewAddress(bool expand) { AddressWidgets widgets = {0}; GtkWidget* vbox; GtkWidget* address = InitGroupContentArea(IDS_AUTOFILL_NEW_ADDRESS, &vbox); + gtk_expander_set_expanded(GTK_EXPANDER(address), expand); + GtkWidget* table = InitFormTable(5, 3); gtk_box_pack_start_defaults(GTK_BOX(vbox), table); @@ -558,6 +637,8 @@ GtkWidget* AutoFillDialog::AddNewAddress() { GtkWidget* button = gtk_button_new_with_label( l10n_util::GetStringUTF8(IDS_AUTOFILL_DELETE_BUTTON).c_str()); + g_signal_connect(button, "clicked", G_CALLBACK(OnDeleteAddressClicked), this); + SetButtonData(button, widgets.label); GtkWidget* alignment = gtk_alignment_new(0, 0, 0, 0); gtk_container_add(GTK_CONTAINER(alignment), button); gtk_box_pack_start_defaults(GTK_BOX(vbox), alignment); @@ -637,12 +718,59 @@ GtkWidget* AutoFillDialog::AddNewCreditCard() { return credit_card; } +void AutoFillDialog::AddAddress(const AutoFillProfile& profile) { + GtkWidget* address = AddNewAddress(false); + gtk_expander_set_label(GTK_EXPANDER(address), + UTF16ToUTF8(profile.Label()).c_str()); + + // We just pushed the widgets to the back of the vector. + const AddressWidgets& widgets = address_widgets_.back(); + SetEntryText(widgets.label, profile.Label()); + SetEntryText(widgets.first_name, + profile.GetFieldText(AutoFillType(NAME_FIRST))); + SetEntryText(widgets.middle_name, + profile.GetFieldText(AutoFillType(NAME_MIDDLE))); + SetEntryText(widgets.last_name, + profile.GetFieldText(AutoFillType(NAME_LAST))); + SetEntryText(widgets.email, + profile.GetFieldText(AutoFillType(EMAIL_ADDRESS))); + SetEntryText(widgets.company_name, + profile.GetFieldText(AutoFillType(COMPANY_NAME))); + SetEntryText(widgets.address_line1, + profile.GetFieldText(AutoFillType(ADDRESS_HOME_LINE1))); + SetEntryText(widgets.address_line2, + profile.GetFieldText(AutoFillType(ADDRESS_HOME_LINE2))); + SetEntryText(widgets.city, + profile.GetFieldText(AutoFillType(ADDRESS_HOME_CITY))); + SetEntryText(widgets.state, + profile.GetFieldText(AutoFillType(ADDRESS_HOME_STATE))); + SetEntryText(widgets.zipcode, + profile.GetFieldText(AutoFillType(ADDRESS_HOME_ZIP))); + SetEntryText(widgets.country, + profile.GetFieldText(AutoFillType(ADDRESS_HOME_COUNTRY))); + SetEntryText(widgets.phone1, + profile.GetFieldText(AutoFillType(PHONE_HOME_COUNTRY_CODE))); + SetEntryText(widgets.phone2, + profile.GetFieldText(AutoFillType(PHONE_HOME_CITY_CODE))); + SetEntryText(widgets.phone3, + profile.GetFieldText(AutoFillType(PHONE_HOME_NUMBER))); + SetEntryText(widgets.fax1, + profile.GetFieldText(AutoFillType(PHONE_FAX_COUNTRY_CODE))); + SetEntryText(widgets.fax2, + profile.GetFieldText(AutoFillType(PHONE_FAX_CITY_CODE))); + SetEntryText(widgets.fax3, + profile.GetFieldText(AutoFillType(PHONE_FAX_NUMBER))); + + gtk_box_pack_start(GTK_BOX(addresses_vbox_), address, FALSE, FALSE, 0); + gtk_widget_show_all(address); +} + /////////////////////////////////////////////////////////////////////////////// // Factory/finder method: void ShowAutoFillDialog(AutoFillDialogObserver* observer, - const std::vector<AutoFillProfile>& profiles, - const std::vector<CreditCard>& credit_cards) { + const std::vector<AutoFillProfile*>& profiles, + const std::vector<CreditCard*>& credit_cards) { if (!dialog) { dialog = new AutoFillDialog(observer, profiles, credit_cards); } diff --git a/chrome/browser/autofill/autofill_manager.cc b/chrome/browser/autofill/autofill_manager.cc index 1fa94d8..2e067a9 100644 --- a/chrome/browser/autofill/autofill_manager.cc +++ b/chrome/browser/autofill/autofill_manager.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. @@ -10,7 +10,6 @@ #include "chrome/browser/autofill/autofill_dialog.h" #include "chrome/browser/autofill/autofill_infobar_delegate.h" #include "chrome/browser/autofill/form_structure.h" -#include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/profile.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/chrome_switches.h" @@ -61,9 +60,24 @@ void AutoFillManager::FormFieldValuesSubmitted( } void AutoFillManager::OnAutoFillDialogApply( - const std::vector<AutoFillProfile>& profiles, - const std::vector<CreditCard>& credit_cards) { - // TODO(jhawkins): Pass the profile data onto the PersonalDataManager. + std::vector<AutoFillProfile>* profiles, + std::vector<CreditCard>* credit_cards) { + // Save the personal data. + personal_data_->SetProfiles(profiles); + // TODO(jhawkins): Set the credit cards. + + HandleSubmit(); +} + +void AutoFillManager::OnPersonalDataLoaded() { + // We might have been alerted that the PersonalDataManager has loaded, so + // remove ourselves as observer. + personal_data_->RemoveObserver(this); + + // TODO(jhawkins): Actually send in the real credit cards from the personal + // data manager. + std::vector<CreditCard*> credit_cards; + ShowAutoFillDialog(this, personal_data_->profiles(), credit_cards); } void AutoFillManager::DeterminePossibleFieldTypes( @@ -93,13 +107,12 @@ void AutoFillManager::OnInfoBarAccepted() { PrefService* prefs = tab_contents_->profile()->GetPrefs(); prefs->SetBoolean(prefs::kAutoFillEnabled, true); - // TODO(jhawkins): Actually send in the real profiles and credit cards from - // the personal data manager. - std::vector<AutoFillProfile> profiles; - std::vector<CreditCard> credit_cards; - ShowAutoFillDialog(this, profiles, credit_cards); - - HandleSubmit(); + // If the personal data manager has not loaded the data yet, set ourselves as + // its observer so that we can listen for the OnPersonalDataLoaded signal. + if (!personal_data_->IsDataLoaded()) + personal_data_->SetObserver(this); + else + OnPersonalDataLoaded(); } void AutoFillManager::SaveFormData() { diff --git a/chrome/browser/autofill/autofill_manager.h b/chrome/browser/autofill/autofill_manager.h index eb53517..85905c8 100644 --- a/chrome/browser/autofill/autofill_manager.h +++ b/chrome/browser/autofill/autofill_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. @@ -9,6 +9,7 @@ #include "base/scoped_ptr.h" #include "chrome/browser/autofill/autofill_dialog.h" +#include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/renderer_host/render_view_host_delegate.h" namespace webkit_glue { @@ -19,14 +20,14 @@ class AutoFillInfoBarDelegate; class AutoFillProfile; class CreditCard; class FormStructure; -class PersonalDataManager; class PrefService; class TabContents; // Manages saving and restoring the user's personal information entered into web // forms. class AutoFillManager : public RenderViewHostDelegate::AutoFill, - AutoFillDialogObserver { + public AutoFillDialogObserver, + public PersonalDataManager::Observer { public: explicit AutoFillManager(TabContents* tab_contents); virtual ~AutoFillManager(); @@ -40,8 +41,11 @@ class AutoFillManager : public RenderViewHostDelegate::AutoFill, // AutoFillDialogObserver implementation: virtual void OnAutoFillDialogApply( - const std::vector<AutoFillProfile>& profiles, - const std::vector<CreditCard>& credit_cards); + std::vector<AutoFillProfile>* profiles, + std::vector<CreditCard>* credit_cards); + + // PersonalDataManager::Observer implementation: + virtual void OnPersonalDataLoaded(); // Uses heuristics and existing personal data to determine the possible field // types. diff --git a/chrome/browser/autofill/autofill_profile.cc b/chrome/browser/autofill/autofill_profile.cc index a292a41..bfc4fef 100644 --- a/chrome/browser/autofill/autofill_profile.cc +++ b/chrome/browser/autofill/autofill_profile.cc @@ -144,8 +144,9 @@ bool AutoFillProfile::operator==(const AutoFillProfile& profile) const { if (label_ != profile.label_ || unique_id_ != profile.unique_id_ || - use_billing_address_ != profile.use_billing_address_) + use_billing_address_ != profile.use_billing_address_) { return false; + } for (size_t index = 0; index < arraysize(types); ++index) { if (GetFieldText(AutoFillType(types[index])) != @@ -183,3 +184,39 @@ Address* AutoFillProfile::GetBillingAddress() { Address* AutoFillProfile::GetHomeAddress() { return static_cast<Address*>(personal_info_[AutoFillType::ADDRESS_HOME]); } + +// So we can compare AutoFillProfiles with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutoFillProfile& profile) { + return os + << UTF16ToASCII(profile.Label()) + << " " + << profile.unique_id() + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_FIRST))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_MIDDLE))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_LAST))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(EMAIL_ADDRESS))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(COMPANY_NAME))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_LINE1))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_LINE2))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_CITY))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_STATE))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_ZIP))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(ADDRESS_HOME_COUNTRY))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType( + PHONE_HOME_WHOLE_NUMBER))) + << " " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType( + PHONE_FAX_WHOLE_NUMBER))); +} diff --git a/chrome/browser/autofill/autofill_profile.h b/chrome/browser/autofill/autofill_profile.h index a6ed0d3..acc7527 100644 --- a/chrome/browser/autofill/autofill_profile.h +++ b/chrome/browser/autofill/autofill_profile.h @@ -53,6 +53,7 @@ class AutoFillProfile : public FormGroup { void operator=(const AutoFillProfile&); // Used by tests. + // TODO(jhawkins): Move these to private and add the test as a friend. bool operator==(const AutoFillProfile& profile) const; void set_label(const string16& label) { label_ = label; } void set_unique_id(int id) { unique_id_ = id; } @@ -75,4 +76,7 @@ class AutoFillProfile : public FormGroup { FormGroupMap personal_info_; }; +// So we can compare AutoFillProfiles with EXPECT_EQ(). +std::ostream& operator<<(std::ostream& os, const AutoFillProfile& profile); + #endif // CHROME_BROWSER_AUTOFILL_AUTOFILL_PROFILE_H_ diff --git a/chrome/browser/autofill/personal_data_manager.cc b/chrome/browser/autofill/personal_data_manager.cc index c60aa6e..e2b2150 100644 --- a/chrome/browser/autofill/personal_data_manager.cc +++ b/chrome/browser/autofill/personal_data_manager.cc @@ -1,12 +1,18 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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 "chrome/browser/autofill/personal_data_manager.h" +#include <algorithm> +#include <iterator> + +#include "base/logging.h" #include "chrome/browser/autofill/autofill_manager.h" #include "chrome/browser/autofill/autofill_field.h" #include "chrome/browser/autofill/form_structure.h" +#include "chrome/browser/profile.h" +#include "chrome/browser/webdata/web_data_service.h" // The minimum number of fields that must contain user data and have known types // before autofill will attempt to import the data into a profile. @@ -19,6 +25,45 @@ static const int kPhoneNumberLength = 7; static const int kPhoneCityCodeLength = 3; PersonalDataManager::~PersonalDataManager() { + CancelPendingQuery(); +} + +void PersonalDataManager::OnWebDataServiceRequestDone( + WebDataService::Handle h, + const WDTypedResult* result) { + // Error from the web database. + if (!result) + return; + + DCHECK(pending_query_handle_); + DCHECK(result->GetType() == AUTOFILL_PROFILES_RESULT); + pending_query_handle_ = 0; + + unique_ids_.clear(); + profiles_.reset(); + const WDResult<std::vector<AutoFillProfile*> >* r = + static_cast<const WDResult<std::vector<AutoFillProfile*> >*>(result); + std::vector<AutoFillProfile*> profiles = r->GetValue(); + for (std::vector<AutoFillProfile*>::iterator iter = profiles.begin(); + iter != profiles.end(); ++iter) { + unique_ids_.insert((*iter)->unique_id()); + profiles_.push_back(*iter); + } + + is_data_loaded_ = true; + if (observer_) + observer_->OnPersonalDataLoaded(); +} + +void PersonalDataManager::SetObserver(PersonalDataManager::Observer* observer) { + DCHECK(observer_ == NULL); + observer_ = observer; +} + +void PersonalDataManager::RemoveObserver( + PersonalDataManager::Observer* observer) { + if (observer_ == observer) + observer_ = NULL; } bool PersonalDataManager::ImportFormData( @@ -31,7 +76,7 @@ bool PersonalDataManager::ImportFormData( int importable_fields = 0; int importable_credit_card_fields = 0; imported_profile_.reset(new AutoFillProfile(string16(), - CreateNextUniqueId())); + CreateNextUniqueID())); imported_credit_card_.reset(new CreditCard(string16())); bool billing_address_info = false; @@ -104,6 +149,63 @@ bool PersonalDataManager::ImportFormData( return true; } +void PersonalDataManager::SetProfiles(std::vector<AutoFillProfile>* profiles) { + if (profile_->IsOffTheRecord()) + return; + + WebDataService* wds = profile_->GetWebDataService(Profile::EXPLICIT_ACCESS); + if (!wds) + return; + + // Remove the unique IDs of the new set of profiles from the unique ID set. + for (std::vector<AutoFillProfile>::iterator iter = profiles->begin(); + iter != profiles->end(); ++iter) { + if (iter->unique_id() != 0) + unique_ids_.erase(iter->unique_id()); + } + + // Any remaining IDs are not in the new profile list and should be removed + // from the web database. + for (std::set<int>::iterator iter = unique_ids_.begin(); + iter != unique_ids_.end(); ++iter) { + wds->RemoveAutoFillProfile(*iter); + } + + // Clear the unique IDs. The set of unique IDs is updated for each profile + // added to |profile_| below. + unique_ids_.clear(); + + // Update the web database with the existing profiles. We need to handle + // these first so that |unique_ids_| is reset with the IDs of the existing + // profiles; otherwise, new profiles added before older profiles can take + // their unique ID. + for (std::vector<AutoFillProfile>::iterator iter = profiles->begin(); + iter != profiles->end(); ++iter) { + if (iter->unique_id() != 0) { + unique_ids_.insert(iter->unique_id()); + wds->UpdateAutoFillProfile(*iter); + } + } + + // Add the new profiles to the web database. + for (std::vector<AutoFillProfile>::iterator iter = profiles->begin(); + iter != profiles->end(); ++iter) { + // Profile was added by the AutoFill dialog, so we need to set the unique + // ID. This also means we need to add this profile to the web DB. + if (iter->unique_id() == 0) { + iter->set_unique_id(CreateNextUniqueID()); + wds->AddAutoFillProfile(*iter); + } + } + + profiles_.reset(); + for (std::vector<AutoFillProfile>::iterator iter = profiles->begin(); + iter != profiles->end(); ++iter) { + profiles_.push_back(new AutoFillProfile(*iter)); + } +} + + void PersonalDataManager::GetPossibleFieldTypes(const string16& text, FieldTypeSet* possible_types) { InitializeIfNeeded(); @@ -115,8 +217,8 @@ void PersonalDataManager::GetPossibleFieldTypes(const string16& text, return; } - ScopedVector<FormGroup>::const_iterator iter; - for (iter = profiles_.begin(); iter != profiles_.end(); ++iter) { + for (ScopedVector<AutoFillProfile>::iterator iter = profiles_.begin(); + iter != profiles_.end(); ++iter) { const FormGroup* profile = *iter; if (!profile) { DLOG(ERROR) << "NULL information in profiles list"; @@ -125,7 +227,8 @@ void PersonalDataManager::GetPossibleFieldTypes(const string16& text, profile->GetPossibleFieldTypes(clean_info, possible_types); } - for (iter = credit_cards_.begin(); iter != credit_cards_.end(); ++iter) { + for (ScopedVector<FormGroup>::iterator iter = credit_cards_.begin(); + iter != credit_cards_.end(); ++iter) { const FormGroup* credit_card = *iter; if (!credit_card) { DLOG(ERROR) << "NULL information in credit cards list"; @@ -143,8 +246,13 @@ bool PersonalDataManager::HasPassword() { return !password_hash_.empty(); } -PersonalDataManager::PersonalDataManager() - : is_initialized_(false) { +PersonalDataManager::PersonalDataManager(Profile* profile) + : profile_(profile), + is_initialized_(false), + is_data_loaded_(false), + pending_query_handle_(0), + observer_(NULL) { + LoadProfiles(); } void PersonalDataManager::InitializeIfNeeded() { @@ -155,7 +263,7 @@ void PersonalDataManager::InitializeIfNeeded() { // TODO(jhawkins): Load data. } -int PersonalDataManager::CreateNextUniqueId() { +int PersonalDataManager::CreateNextUniqueID() { // Profile IDs MUST start at 1 to allow 0 as an error value when reading // the ID from the WebDB (see LoadData()). int id = 1; @@ -190,3 +298,29 @@ void PersonalDataManager::ParsePhoneNumber( // Treat any remaining digits as the country code. profile->SetInfo(AutoFillType(country_code), *value); } + +void PersonalDataManager::LoadProfiles() { + WebDataService* web_data_service = + profile_->GetWebDataService(Profile::EXPLICIT_ACCESS); + if (!web_data_service) { + NOTREACHED(); + return; + } + + CancelPendingQuery(); + + pending_query_handle_ = web_data_service->GetAutoFillProfiles(this); +} + +void PersonalDataManager::CancelPendingQuery() { + if (pending_query_handle_) { + WebDataService* web_data_service = + profile_->GetWebDataService(Profile::EXPLICIT_ACCESS); + if (!web_data_service) { + NOTREACHED(); + return; + } + web_data_service->CancelRequest(pending_query_handle_); + } + pending_query_handle_ = 0; +} diff --git a/chrome/browser/autofill/personal_data_manager.h b/chrome/browser/autofill/personal_data_manager.h index c28c24b..5821a2a 100644 --- a/chrome/browser/autofill/personal_data_manager.h +++ b/chrome/browser/autofill/personal_data_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2010 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. @@ -14,17 +14,41 @@ #include "chrome/browser/autofill/autofill_profile.h" #include "chrome/browser/autofill/credit_card.h" #include "chrome/browser/autofill/field_types.h" +#include "chrome/browser/webdata/web_data_service.h" class AutoFillManager; class FormStructure; +class Profile; // Handles loading and saving AutoFill profile information to the web database. // This class also stores the profiles loaded from the database for use during // AutoFill. -class PersonalDataManager { +class PersonalDataManager : public WebDataServiceConsumer { public: + // An interface the PersonalDataManager uses to notify its clients (observers) + // when it has finished loading personal data from the web database. Register + // the observer via PersonalDataManager::SetObserver. + class Observer { + public: + // Notifies the observer that the PersonalDataManager has finished loading. + virtual void OnPersonalDataLoaded() = 0; + + protected: + virtual ~Observer() {} + }; + virtual ~PersonalDataManager(); + // WebDataServiceConsumer implementation: + virtual void OnWebDataServiceRequestDone(WebDataService::Handle h, + const WDTypedResult* result); + + // Sets the listener to be notified of PersonalDataManager events. + void SetObserver(PersonalDataManager::Observer* observer); + + // Removes |observer| as the observer of this PersonalDataManager. + void RemoveObserver(PersonalDataManager::Observer* observer); + // If AutoFill is able to determine the field types of a significant number // of field types that contain information in the FormStructures and the user // has not previously been prompted, the user will be asked if he would like @@ -33,6 +57,10 @@ class PersonalDataManager { bool ImportFormData(const std::vector<FormStructure*>& form_structures, AutoFillManager* autofill_manager); + // Sets |profiles_| to the contents of |profiles| and updates the web database + // by adding, updating and removing profiles. + void SetProfiles(std::vector<AutoFillProfile>* profiles); + // Gets the possible field types for the given text, determined by matching // the text with all known personal information and returning matching types. void GetPossibleFieldTypes(const string16& text, @@ -41,19 +69,31 @@ class PersonalDataManager { // Returns true if the credit card information is stored with a password. bool HasPassword(); + // Returns whether the personal data has been loaded from the web database. + bool IsDataLoaded() const { return is_data_loaded_; } + + // This PersonalDataManager owns these profiles. Their lifetime is until the + // web database is updated with new profile information. + const std::vector<AutoFillProfile*>& profiles() { return profiles_.get(); } + private: - // Make sure that only Profile can create an instance of PersonalDataManager. + // Make sure that only Profile and the PersonalDataManager tests can create an + // instance of PersonalDataManager. friend class ProfileImpl; + friend class PersonalDataManagerTest; + + explicit PersonalDataManager(Profile* profile); - PersonalDataManager(); + // Returns the profile of the tab contents. + Profile* profile(); // Initializes the object if needed. This should be called at the beginning // of all the public functions to make sure that the object has been properly // initialized before use. void InitializeIfNeeded(); - // This will create and reserve a new unique id for a profile. - int CreateNextUniqueId(); + // This will create and reserve a new unique ID for a profile. + int CreateNextUniqueID(); // Parses value to extract the components of a phone number and adds them to // profile. @@ -65,14 +105,26 @@ class PersonalDataManager { AutoFillFieldType city_code, AutoFillFieldType country_code) const; + // Loads the saved profiles from the web database. + void LoadProfiles(); + + // Cancels a pending query to the web database. + void CancelPendingQuery(); + + // The profile hosting this PersonalDataManager. + Profile* profile_; + // True if PersonalDataManager is initialized. bool is_initialized_; + // True if personal data has been loaded from the web database. + bool is_data_loaded_; + // The set of already created unique IDs, used to create a new unique ID. std::set<int> unique_ids_; // The loaded profiles. - ScopedVector<FormGroup> profiles_; + ScopedVector<AutoFillProfile> profiles_; // The loaded credit cards. ScopedVector<FormGroup> credit_cards_; @@ -86,6 +138,16 @@ class PersonalDataManager { // The hash of the password used to store the credit card. This is empty if // no password exists. string16 password_hash_; + + // When the manager makes a request from WebDataService, the database + // is queried on another thread, we record the query handle until we + // get called back. + WebDataService::Handle pending_query_handle_; + + // The observer. This can be NULL. + Observer* observer_; + + DISALLOW_COPY_AND_ASSIGN(PersonalDataManager); }; #endif // CHROME_BROWSER_AUTOFILL_PERSONAL_DATA_MANAGER_H_ diff --git a/chrome/browser/autofill/personal_data_manager_unittest.cc b/chrome/browser/autofill/personal_data_manager_unittest.cc new file mode 100644 index 0000000..5bcbb08 --- /dev/null +++ b/chrome/browser/autofill/personal_data_manager_unittest.cc @@ -0,0 +1,176 @@ +// Copyright (c) 2010 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 "base/basictypes.h" +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/autofill/autofill_profile.h" +#include "chrome/browser/autofill/personal_data_manager.h" +#include "chrome/browser/chrome_thread.h" +#include "chrome/common/notification_details.h" +#include "chrome/common/notification_observer_mock.h" +#include "chrome/common/notification_registrar.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_type.h" +#include "chrome/test/testing_profile.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +ACTION(QuitUIMessageLoop) { + DCHECK(ChromeThread::CurrentlyOn(ChromeThread::UI)); + MessageLoop::current()->Quit(); +} + +class PersonalDataLoadedObserverMock : public PersonalDataManager::Observer { + public: + PersonalDataLoadedObserverMock() {} + virtual ~PersonalDataLoadedObserverMock() {} + + MOCK_METHOD0(OnPersonalDataLoaded, void()); +}; + +class PersonalDataManagerTest : public testing::Test { + protected: + PersonalDataManagerTest() + : ui_thread_(ChromeThread::UI, &message_loop_), + db_thread_(ChromeThread::DB) { + } + + virtual void SetUp() { + db_thread_.Start(); + + profile_.reset(new TestingProfile); + profile_->CreateWebDataService(false); + + ResetPersonalDataManager(); + } + + virtual void TearDown() { + if (profile_.get()) + profile_.reset(NULL); + + db_thread_.Stop(); + MessageLoop::current()->PostTask(FROM_HERE, new MessageLoop::QuitTask); + MessageLoop::current()->Run(); + } + + void ResetPersonalDataManager() { + personal_data_.reset(new PersonalDataManager(profile_.get())); + personal_data_->SetObserver(&personal_data_observer_); + } + + static void SetProfileInfo(AutoFillProfile* profile, + const char* label, const char* first_name, const char* middle_name, + const char* last_name, const char* email, const char* company, + const char* address1, const char* address2, const char* city, + const char* state, const char* zipcode, const char* country, + const char* phone, const char* fax) { + profile->set_label(ASCIIToUTF16(label)); + profile->SetInfo(AutoFillType(NAME_FIRST), ASCIIToUTF16(first_name)); + profile->SetInfo(AutoFillType(NAME_MIDDLE), ASCIIToUTF16(middle_name)); + profile->SetInfo(AutoFillType(NAME_LAST), ASCIIToUTF16(last_name)); + profile->SetInfo(AutoFillType(EMAIL_ADDRESS), ASCIIToUTF16(email)); + profile->SetInfo(AutoFillType(COMPANY_NAME), ASCIIToUTF16(company)); + profile->SetInfo(AutoFillType(ADDRESS_HOME_LINE1), ASCIIToUTF16(address1)); + profile->SetInfo(AutoFillType(ADDRESS_HOME_LINE2), ASCIIToUTF16(address2)); + profile->SetInfo(AutoFillType(ADDRESS_HOME_CITY), ASCIIToUTF16(city)); + profile->SetInfo(AutoFillType(ADDRESS_HOME_STATE), ASCIIToUTF16(state)); + profile->SetInfo(AutoFillType(ADDRESS_HOME_ZIP), ASCIIToUTF16(zipcode)); + profile->SetInfo(AutoFillType(ADDRESS_HOME_COUNTRY), ASCIIToUTF16(country)); + profile->SetInfo(AutoFillType(PHONE_HOME_NUMBER), ASCIIToUTF16(phone)); + profile->SetInfo(AutoFillType(PHONE_FAX_NUMBER), ASCIIToUTF16(fax)); + } + + MessageLoopForUI message_loop_; + ChromeThread ui_thread_; + ChromeThread db_thread_; + scoped_ptr<TestingProfile> profile_; + scoped_ptr<PersonalDataManager> personal_data_; + NotificationRegistrar registrar_; + NotificationObserverMock observer_; + PersonalDataLoadedObserverMock personal_data_observer_; +}; + +// TODO(jhawkins): Test SaveProfiles w/out a WebDataService in the profile. +TEST_F(PersonalDataManagerTest, SetProfiles) { + AutoFillProfile profile0(string16(), 0); + SetProfileInfo(&profile0, "Billing", "Marion", "Mitchell", "Morrison", + "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", + "91601", "US", "12345678910", "01987654321"); + + AutoFillProfile profile1(string16(), 0); + SetProfileInfo(&profile1, "Home", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "903 Apple Ct.", NULL, "Orlando", "FL", "32801", + "US", "19482937549", "13502849239"); + + AutoFillProfile profile2(string16(), 0); + SetProfileInfo(&profile2, "Work", "Josephine", "Alicia", "Saenz", + "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", + "32801", "US", "19482937549", "13502849239"); + + // This will verify that the web database has been loaded and the notification + // sent out. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataLoaded()).WillOnce(QuitUIMessageLoop()); + + // The message loop will exit when the mock observer is notified. + MessageLoop::current()->Run(); + + // Add the three test profiles to the database. + std::vector<AutoFillProfile> update; + update.push_back(profile0); + update.push_back(profile1); + personal_data_->SetProfiles(&update); + + // The PersonalDataManager will update the unique IDs when saving the + // profiles, so we have to update the expectations. + profile0.set_unique_id(update[0].unique_id()); + profile1.set_unique_id(update[1].unique_id()); + + const std::vector<AutoFillProfile*>& results1 = personal_data_->profiles(); + ASSERT_EQ(2U, results1.size()); + EXPECT_EQ(profile0, *results1.at(0)); + EXPECT_EQ(profile1, *results1.at(1)); + + // Three operations in one: + // - Update profile0 + // - Remove profile1 + // - Add profile2 + profile0.SetInfo(AutoFillType(NAME_FIRST), ASCIIToUTF16("John")); + update.clear(); + update.push_back(profile0); + update.push_back(profile2); + personal_data_->SetProfiles(&update); + + // Set the expected unique ID for profile2. + profile2.set_unique_id(update[1].unique_id()); + + // AutoFillProfile IDs are re-used, so the third profile to be added will have + // a unique ID that matches the old unique ID of the removed profile1, even + // though that ID has already been used. + const std::vector<AutoFillProfile*>& results2 = personal_data_->profiles(); + ASSERT_EQ(2U, results2.size()); + EXPECT_EQ(profile0, *results2.at(0)); + EXPECT_EQ(profile2, *results2.at(1)); + EXPECT_EQ(profile2.unique_id(), profile1.unique_id()); + + // Reset the PersonalDataManager. This tests that the personal data was saved + // to the web database, and that we can load the profiles from the web + // database. + ResetPersonalDataManager(); + + // This will verify that the web database has been loaded and the notification + // sent out. + EXPECT_CALL(personal_data_observer_, + OnPersonalDataLoaded()).WillOnce(QuitUIMessageLoop()); + + // The message loop will exit when the PersonalDataLoadedObserver is notified. + MessageLoop::current()->Run(); + + // Verify that we've loaded the profiles from the web database. + const std::vector<AutoFillProfile*>& results3 = personal_data_->profiles(); + ASSERT_EQ(2U, results3.size()); + EXPECT_EQ(profile0, *results3.at(0)); + EXPECT_EQ(profile2, *results3.at(1)); +} |