| // Copyright (c) 2012 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/utf_string_conversions.h" |
| #include "chrome/browser/autofill/autocomplete_history_manager.h" |
| #include "chrome/browser/autofill/autofill_external_delegate.h" |
| #include "chrome/browser/autofill/autofill_manager.h" |
| #include "chrome/browser/ui/autofill/autofill_popup_controller_impl.h" |
| #include "chrome/common/autofill_messages.h" |
| #include "chrome/common/chrome_constants.h" |
| #include "content/public/browser/navigation_controller.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/notification_types.h" |
| #include "content/public/browser/render_view_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/browser/web_contents_view.h" |
| #include "grit/chromium_strings.h" |
| #include "grit/generated_resources.h" |
| #include "third_party/WebKit/Source/WebKit/chromium/public/WebAutofillClient.h" |
| #include "ui/base/l10n/l10n_util.h" |
| |
| #if defined(OS_ANDROID) |
| #include "content/public/browser/android/content_view_core.h" |
| #endif |
| |
| using content::RenderViewHost; |
| using WebKit::WebAutofillClient; |
| |
| DEFINE_WEB_CONTENTS_USER_DATA_KEY(AutofillExternalDelegate); |
| |
| void AutofillExternalDelegate::CreateForWebContentsAndManager( |
| content::WebContents* web_contents, |
| AutofillManager* autofill_manager) { |
| if (FromWebContents(web_contents)) |
| return; |
| |
| web_contents->SetUserData( |
| UserDataKey(), |
| new AutofillExternalDelegate(web_contents, autofill_manager)); |
| } |
| |
| AutofillExternalDelegate::AutofillExternalDelegate( |
| content::WebContents* web_contents, |
| AutofillManager* autofill_manager) |
| : web_contents_(web_contents), |
| autofill_manager_(autofill_manager), |
| controller_(NULL), |
| password_autofill_manager_(web_contents), |
| autofill_query_id_(0), |
| display_warning_if_disabled_(false), |
| has_shown_autofill_popup_for_current_edit_(false) { |
| registrar_.Add(this, |
| content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED, |
| content::Source<content::WebContents>(web_contents)); |
| if (web_contents) { |
| registrar_.Add( |
| this, |
| content::NOTIFICATION_NAV_ENTRY_COMMITTED, |
| content::Source<content::NavigationController>( |
| &(web_contents->GetController()))); |
| } |
| } |
| |
| AutofillExternalDelegate::~AutofillExternalDelegate() { |
| if (controller_) |
| controller_->Hide(); |
| } |
| |
| void AutofillExternalDelegate::OnQuery(int query_id, |
| const FormData& form, |
| const FormFieldData& field, |
| const gfx::Rect& element_bounds, |
| bool display_warning_if_disabled) { |
| autofill_query_form_ = form; |
| autofill_query_field_ = field; |
| display_warning_if_disabled_ = display_warning_if_disabled; |
| autofill_query_id_ = query_id; |
| |
| EnsurePopupForElement(element_bounds); |
| } |
| |
| void AutofillExternalDelegate::OnSuggestionsReturned( |
| int query_id, |
| const std::vector<string16>& autofill_values, |
| const std::vector<string16>& autofill_labels, |
| const std::vector<string16>& autofill_icons, |
| const std::vector<int>& autofill_unique_ids) { |
| if (query_id != autofill_query_id_ || !controller_) |
| return; |
| |
| std::vector<string16> values(autofill_values); |
| std::vector<string16> labels(autofill_labels); |
| std::vector<string16> icons(autofill_icons); |
| std::vector<int> ids(autofill_unique_ids); |
| |
| // Add a separator to go between the values and menu items. |
| values.push_back(string16()); |
| labels.push_back(string16()); |
| icons.push_back(string16()); |
| ids.push_back(WebAutofillClient::MenuItemIDSeparator); |
| |
| ApplyAutofillWarnings(&values, &labels, &icons, &ids); |
| |
| // Only include "Autofill Options" special menu item if we have Autofill |
| // items, identified by |unique_ids| having at least one valid value. |
| bool has_autofill_item = false; |
| for (size_t i = 0; i < ids.size(); ++i) { |
| if (ids[i] > 0) { |
| has_autofill_item = true; |
| break; |
| } |
| } |
| |
| if (has_autofill_item) |
| ApplyAutofillOptions(&values, &labels, &icons, &ids); |
| |
| // Remove the separator if it is the last element. |
| if (ids.back() == WebAutofillClient::MenuItemIDSeparator) { |
| values.pop_back(); |
| labels.pop_back(); |
| icons.pop_back(); |
| ids.pop_back(); |
| } |
| |
| InsertDataListValues(&values, &labels, &icons, &ids); |
| |
| if (values.empty()) { |
| // No suggestions, any popup currently showing is obsolete. |
| HideAutofillPopup(); |
| return; |
| } |
| |
| // Send to display. |
| if (autofill_query_field_.is_focusable) { |
| ApplyAutofillSuggestions(values, labels, icons, ids); |
| |
| if (autofill_manager_) { |
| autofill_manager_->OnDidShowAutofillSuggestions( |
| has_autofill_item && !has_shown_autofill_popup_for_current_edit_); |
| } |
| has_shown_autofill_popup_for_current_edit_ |= has_autofill_item; |
| } |
| } |
| |
| void AutofillExternalDelegate::OnShowPasswordSuggestions( |
| const std::vector<string16>& suggestions, |
| const FormFieldData& field, |
| const gfx::Rect& element_bounds) { |
| autofill_query_field_ = field; |
| EnsurePopupForElement(element_bounds); |
| |
| if (suggestions.empty()) { |
| HideAutofillPopup(); |
| return; |
| } |
| |
| std::vector<string16> empty(suggestions.size()); |
| std::vector<int> password_ids(suggestions.size(), |
| WebAutofillClient::MenuItemIDPasswordEntry); |
| ApplyAutofillSuggestions(suggestions, empty, empty, password_ids); |
| } |
| |
| void AutofillExternalDelegate::EnsurePopupForElement( |
| const gfx::Rect& element_bounds) { |
| // Convert element_bounds to be in screen space. If |web_contents_| is NULL |
| // then assume the element_bounds is already in screen space (since we don't |
| // have any other way of converting to screen space). |
| gfx::Rect element_bounds_in_screen_space = element_bounds; |
| if (web_contents_) { |
| gfx::Rect client_area; |
| web_contents_->GetContainerBounds(&client_area); |
| element_bounds_in_screen_space += client_area.OffsetFromOrigin(); |
| } |
| |
| // |controller_| owns itself. |
| controller_ = AutofillPopupControllerImpl::GetOrCreate( |
| controller_, |
| this, |
| // web_contents() may be NULL during testing. |
| web_contents() ? web_contents()->GetView()->GetContentNativeView() : NULL, |
| element_bounds_in_screen_space); |
| } |
| |
| void AutofillExternalDelegate::ApplyAutofillSuggestions( |
| const std::vector<string16>& autofill_values, |
| const std::vector<string16>& autofill_labels, |
| const std::vector<string16>& autofill_icons, |
| const std::vector<int>& autofill_unique_ids) { |
| controller_->Show(autofill_values, |
| autofill_labels, |
| autofill_icons, |
| autofill_unique_ids); |
| |
| web_contents()->GetRenderViewHost()->AddKeyboardListener(controller_); |
| } |
| |
| void AutofillExternalDelegate::SetCurrentDataListValues( |
| const std::vector<string16>& data_list_values, |
| const std::vector<string16>& data_list_labels, |
| const std::vector<string16>& data_list_icons, |
| const std::vector<int>& data_list_unique_ids) { |
| data_list_values_ = data_list_values; |
| data_list_labels_ = data_list_labels; |
| data_list_icons_ = data_list_icons; |
| data_list_unique_ids_ = data_list_unique_ids; |
| } |
| |
| void AutofillExternalDelegate::DidSelectSuggestion(int identifier) { |
| ClearPreviewedForm(); |
| |
| // Only preview the data if it is a profile. |
| if (identifier > 0) |
| FillAutofillFormData(identifier, true); |
| } |
| |
| void AutofillExternalDelegate::DidAcceptSuggestion(const string16& value, |
| int identifier) { |
| RenderViewHost* host = web_contents_->GetRenderViewHost(); |
| |
| if (identifier == WebAutofillClient::MenuItemIDAutofillOptions) { |
| // User selected 'Autofill Options'. |
| autofill_manager_->OnShowAutofillDialog(); |
| } else if (identifier == WebAutofillClient::MenuItemIDClearForm) { |
| // User selected 'Clear form'. |
| host->Send(new AutofillMsg_ClearForm(host->GetRoutingID())); |
| } else if (identifier == WebAutofillClient::MenuItemIDPasswordEntry && |
| password_autofill_manager_.DidAcceptAutofillSuggestion( |
| autofill_query_field_, value)) { |
| // DidAcceptAutofillSuggestion has already handled the work to fill in |
| // the page as required. |
| } else if (identifier == WebAutofillClient::MenuItemIDDataListEntry) { |
| host->Send(new AutofillMsg_AcceptDataListSuggestion(host->GetRoutingID(), |
| value)); |
| } else if (identifier == WebAutofillClient::MenuItemIDAutocompleteEntry) { |
| // User selected an Autocomplete, so we fill directly. |
| host->Send(new AutofillMsg_SetNodeText(host->GetRoutingID(), value)); |
| } else { |
| FillAutofillFormData(identifier, false); |
| } |
| |
| HideAutofillPopup(); |
| } |
| |
| void AutofillExternalDelegate::RemoveSuggestion(const string16& value, |
| int identifier) { |
| if (identifier > 0) { |
| autofill_manager_->RemoveAutofillProfileOrCreditCard(identifier); |
| } else if (web_contents_) { |
| autofill_manager_->RemoveAutocompleteEntry(autofill_query_field_.name, |
| value); |
| } |
| } |
| |
| void AutofillExternalDelegate::DidEndTextFieldEditing() { |
| HideAutofillPopup(); |
| |
| has_shown_autofill_popup_for_current_edit_ = false; |
| } |
| |
| void AutofillExternalDelegate::ClearPreviewedForm() { |
| if (web_contents_) { |
| RenderViewHost* host = web_contents_->GetRenderViewHost(); |
| |
| if (host) |
| host->Send(new AutofillMsg_ClearPreviewedForm(host->GetRoutingID())); |
| } |
| } |
| |
| void AutofillExternalDelegate::ControllerDestroyed() { |
| web_contents()->GetRenderViewHost()->RemoveKeyboardListener(controller_); |
| controller_ = NULL; |
| } |
| |
| void AutofillExternalDelegate::HideAutofillPopup() { |
| if (controller_) { |
| controller_->Hide(); |
| // Go ahead and invalidate |controller_|. After calling Hide(), it won't |
| // inform |this| of its destruction. |
| ControllerDestroyed(); |
| } |
| } |
| |
| void AutofillExternalDelegate::Reset() { |
| HideAutofillPopup(); |
| |
| password_autofill_manager_.Reset(); |
| } |
| |
| void AutofillExternalDelegate::AddPasswordFormMapping( |
| const FormFieldData& form, |
| const PasswordFormFillData& fill_data) { |
| password_autofill_manager_.AddPasswordFormMapping(form, fill_data); |
| } |
| |
| void AutofillExternalDelegate::FillAutofillFormData(int unique_id, |
| bool is_preview) { |
| // If the selected element is a warning we don't want to do anything. |
| if (unique_id == WebAutofillClient::MenuItemIDWarningMessage) |
| return; |
| |
| RenderViewHost* host = web_contents_->GetRenderViewHost(); |
| |
| if (is_preview) { |
| host->Send(new AutofillMsg_SetAutofillActionPreview( |
| host->GetRoutingID())); |
| } else { |
| host->Send(new AutofillMsg_SetAutofillActionFill( |
| host->GetRoutingID())); |
| } |
| |
| // Fill the values for the whole form. |
| autofill_manager_->OnFillAutofillFormData(autofill_query_id_, |
| autofill_query_form_, |
| autofill_query_field_, |
| unique_id); |
| } |
| |
| void AutofillExternalDelegate::ApplyAutofillWarnings( |
| std::vector<string16>* autofill_values, |
| std::vector<string16>* autofill_labels, |
| std::vector<string16>* autofill_icons, |
| std::vector<int>* autofill_unique_ids) { |
| if (!autofill_query_field_.should_autocomplete) { |
| // If autofill is disabled and we had suggestions, show a warning instead. |
| autofill_values->assign( |
| 1, l10n_util::GetStringUTF16(IDS_AUTOFILL_WARNING_FORM_DISABLED)); |
| autofill_labels->assign(1, string16()); |
| autofill_icons->assign(1, string16()); |
| autofill_unique_ids->assign(1, WebAutofillClient::MenuItemIDWarningMessage); |
| } else if (autofill_unique_ids->size() > 1 && |
| (*autofill_unique_ids)[0] == |
| WebAutofillClient::MenuItemIDWarningMessage) { |
| // If we received a warning instead of suggestions from autofill but regular |
| // suggestions from autocomplete, don't show the autofill warning. |
| autofill_values->erase(autofill_values->begin()); |
| autofill_labels->erase(autofill_labels->begin()); |
| autofill_icons->erase(autofill_icons->begin()); |
| autofill_unique_ids->erase(autofill_unique_ids->begin()); |
| } |
| |
| // If we were about to show a warning and we shouldn't, don't. |
| if (!autofill_unique_ids->empty() && |
| (*autofill_unique_ids)[0] == |
| WebAutofillClient::MenuItemIDWarningMessage && |
| !display_warning_if_disabled_) { |
| autofill_values->clear(); |
| autofill_labels->clear(); |
| autofill_icons->clear(); |
| autofill_unique_ids->clear(); |
| } |
| } |
| |
| void AutofillExternalDelegate::ApplyAutofillOptions( |
| std::vector<string16>* autofill_values, |
| std::vector<string16>* autofill_labels, |
| std::vector<string16>* autofill_icons, |
| std::vector<int>* autofill_unique_ids) { |
| // The form has been auto-filled, so give the user the chance to clear the |
| // form. Append the 'Clear form' menu item. |
| if (autofill_query_field_.is_autofilled) { |
| autofill_values->push_back( |
| l10n_util::GetStringUTF16(IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM)); |
| autofill_labels->push_back(string16()); |
| autofill_icons->push_back(string16()); |
| autofill_unique_ids->push_back(WebAutofillClient::MenuItemIDClearForm); |
| } |
| |
| // Append the 'Chrome Autofill options' menu item; |
| autofill_values->push_back( |
| l10n_util::GetStringUTF16(IDS_AUTOFILL_OPTIONS_POPUP)); |
| autofill_labels->push_back(string16()); |
| autofill_icons->push_back(string16()); |
| autofill_unique_ids->push_back(WebAutofillClient::MenuItemIDAutofillOptions); |
| } |
| |
| void AutofillExternalDelegate::InsertDataListValues( |
| std::vector<string16>* autofill_values, |
| std::vector<string16>* autofill_labels, |
| std::vector<string16>* autofill_icons, |
| std::vector<int>* autofill_unique_ids) { |
| if (data_list_values_.empty()) |
| return; |
| |
| // Insert the separator between the datalist and Autofill values (if there |
| // are any). |
| if (!autofill_values->empty()) { |
| autofill_values->insert(autofill_values->begin(), string16()); |
| autofill_labels->insert(autofill_labels->begin(), string16()); |
| autofill_icons->insert(autofill_icons->begin(), string16()); |
| autofill_unique_ids->insert(autofill_unique_ids->begin(), |
| WebAutofillClient::MenuItemIDSeparator); |
| } |
| |
| // Insert the datalist elements. |
| autofill_values->insert(autofill_values->begin(), |
| data_list_values_.begin(), |
| data_list_values_.end()); |
| autofill_labels->insert(autofill_labels->begin(), |
| data_list_labels_.begin(), |
| data_list_labels_.end()); |
| autofill_icons->insert(autofill_icons->begin(), |
| data_list_icons_.begin(), |
| data_list_icons_.end()); |
| autofill_unique_ids->insert(autofill_unique_ids->begin(), |
| data_list_unique_ids_.begin(), |
| data_list_unique_ids_.end()); |
| } |
| |
| void AutofillExternalDelegate::Observe( |
| int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) { |
| if (type == content::NOTIFICATION_WEB_CONTENTS_VISIBILITY_CHANGED) { |
| if (!*content::Details<bool>(details).ptr()) |
| HideAutofillPopup(); |
| } else if (type == content::NOTIFICATION_NAV_ENTRY_COMMITTED) { |
| HideAutofillPopup(); |
| } else { |
| NOTREACHED(); |
| } |
| } |