blob: 0e3f72693b49b529996c55ee9ddfb0c42ef33b35 [file] [log] [blame]
// 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/renderer/autofill_helper.h"
#include "app/l10n_util.h"
#include "chrome/renderer/form_manager.h"
#include "chrome/renderer/render_view.h"
#include "grit/generated_resources.h"
#include "third_party/WebKit/WebKit/chromium/public/WebDocument.h"
#include "third_party/WebKit/WebKit/chromium/public/WebInputEvent.h"
#include "third_party/WebKit/WebKit/chromium/public/WebFormControlElement.h"
#include "third_party/WebKit/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/WebKit/chromium/public/WebView.h"
#include "webkit/glue/password_form.h"
using WebKit::WebFormControlElement;
using WebKit::WebFormElement;
using WebKit::WebFrame;
using WebKit::WebInputElement;
using WebKit::WebNode;
using WebKit::WebString;
namespace {
// The size above which we stop triggering autofill for an input text field
// (so to avoid sending long strings through IPC).
const size_t kMaximumTextSizeForAutoFill = 1000;
} // namespace
AutoFillHelper::AutoFillHelper(RenderView* render_view)
: render_view_(render_view),
autofill_query_id_(0),
autofill_action_(AUTOFILL_NONE),
suggestions_clear_index_(-1),
suggestions_options_index_(-1) {
}
void AutoFillHelper::QueryAutoFillSuggestions(const WebNode& node,
const WebString& name,
const WebString& value) {
static int query_counter = 0;
autofill_query_id_ = query_counter++;
autofill_query_node_ = node;
const WebFormControlElement& element = node.toConst<WebFormControlElement>();
webkit_glue::FormField field;
FormManager::WebFormControlElementToFormField(element, true, &field);
// WebFormControlElementToFormField does not scrape the DOM for the field
// label, so find the label here.
// TODO(jhawkins): Add form and field identities so we can use the cached form
// data in FormManager.
field.set_label(FormManager::LabelForElement(element));
bool form_autofilled = form_manager_.FormWithNodeIsAutoFilled(node);
render_view_->Send(new ViewHostMsg_QueryFormFieldAutoFill(
render_view_->routing_id(), autofill_query_id_, form_autofilled, field));
}
void AutoFillHelper::RemoveAutocompleteSuggestion(
const WebKit::WebString& name, const WebKit::WebString& value) {
// The index of clear & options will have shifted down.
if (suggestions_clear_index_ != -1)
suggestions_clear_index_--;
if (suggestions_options_index_ != -1)
suggestions_options_index_--;
render_view_->Send(new ViewHostMsg_RemoveAutocompleteEntry(
render_view_->routing_id(), name, value));
}
void AutoFillHelper::SuggestionsReceived(int query_id,
const std::vector<string16>& values,
const std::vector<string16>& labels,
const std::vector<string16>& icons,
const std::vector<int>& unique_ids) {
WebKit::WebView* web_view = render_view_->webview();
if (!web_view || query_id != autofill_query_id_)
return;
// Any popup currently showing is now obsolete.
web_view->hidePopups();
// No suggestions: nothing to do.
if (values.empty())
return;
std::vector<string16> v(values);
std::vector<string16> l(labels);
std::vector<string16> i(icons);
std::vector<int> ids(unique_ids);
int separator_index = -1;
// The form has been auto-filled, so give the user the chance to clear the
// form. Append the 'Clear form' menu item.
if (form_manager_.FormWithNodeIsAutoFilled(autofill_query_node_)) {
v.push_back(l10n_util::GetStringUTF16(IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM));
l.push_back(string16());
i.push_back(string16());
ids.push_back(0);
suggestions_clear_index_ = v.size() - 1;
separator_index = values.size();
}
// Only include "AutoFill Options" special menu item if we have AutoFill
// items, identified by |unique_ids| having at least one valid value.
bool show_options = false;
for (size_t i = 0; i < ids.size(); ++i) {
if (ids[i] != 0) {
show_options = true;
break;
}
}
if (show_options) {
// Append the 'AutoFill Options...' menu item.
v.push_back(l10n_util::GetStringUTF16(IDS_AUTOFILL_OPTIONS));
l.push_back(string16());
i.push_back(string16());
ids.push_back(0);
suggestions_options_index_ = v.size() - 1;
separator_index = values.size();
}
// Send to WebKit for display.
if (!v.empty()) {
web_view->applyAutoFillSuggestions(
autofill_query_node_, v, l, i, ids, separator_index);
}
}
void AutoFillHelper::FormDataFilled(int query_id,
const webkit_glue::FormData& form) {
if (!render_view_->webview() || query_id != autofill_query_id_)
return;
switch (autofill_action_) {
case AUTOFILL_FILL:
form_manager_.FillForm(form, autofill_query_node_);
break;
case AUTOFILL_PREVIEW:
form_manager_.PreviewForm(form);
break;
default:
NOTREACHED();
}
autofill_action_ = AUTOFILL_NONE;
}
void AutoFillHelper::DidSelectAutoFillSuggestion(const WebNode& node,
const WebString& value,
const WebString& label,
int unique_id) {
DidClearAutoFillSelection(node);
QueryAutoFillFormData(node, value, label, unique_id, AUTOFILL_PREVIEW);
}
void AutoFillHelper::DidAcceptAutoFillSuggestion(const WebNode& node,
const WebString& value,
const WebString& label,
int unique_id,
unsigned index) {
if (suggestions_options_index_ != -1 &&
index == static_cast<unsigned>(suggestions_options_index_)) {
// User selected 'AutoFill Options'.
render_view_->Send(new ViewHostMsg_ShowAutoFillDialog(
render_view_->routing_id()));
} else if (suggestions_clear_index_ != -1 &&
index == static_cast<unsigned>(suggestions_clear_index_)) {
// User selected 'Clear form'.
// The form has been auto-filled, so give the user the chance to clear the
// form.
form_manager_.ClearFormWithNode(node);
} else if (form_manager_.FormWithNodeIsAutoFilled(node) || !unique_id) {
// User selected an Autocomplete entry, so we fill directly.
WebInputElement element = node.toConst<WebInputElement>();
// Set the suggested value to update input element value immediately in UI.
// The |setValue| call has update delayed until element loses focus.
string16 substring = value;
substring = substring.substr(0, element.maxLength());
element.setSuggestedValue(substring);
element.setValue(substring);
WebFrame* webframe = node.document().frame();
if (webframe)
webframe->notifiyPasswordListenerOfAutocomplete(element);
} else {
// Fill the values for the whole form.
QueryAutoFillFormData(node, value, label, unique_id, AUTOFILL_FILL);
}
suggestions_clear_index_ = -1;
suggestions_options_index_ = -1;
}
void AutoFillHelper::DidClearAutoFillSelection(const WebNode& node) {
form_manager_.ClearPreviewedFormWithNode(node);
}
void AutoFillHelper::FrameContentsAvailable(WebFrame* frame) {
form_manager_.ExtractForms(frame);
SendForms(frame);
}
void AutoFillHelper::FrameWillClose(WebFrame* frame) {
form_manager_.ResetFrame(frame);
}
void AutoFillHelper::FrameDetached(WebFrame* frame) {
form_manager_.ResetFrame(frame);
}
void AutoFillHelper::TextDidChangeInTextField(const WebInputElement& element) {
ShowSuggestions(element, false, true);
}
void AutoFillHelper::InputElementClicked(const WebInputElement& element,
bool already_focused) {
if (already_focused)
ShowSuggestions(element, true, false);
}
void AutoFillHelper::ShowSuggestions(
const WebInputElement& const_element,
bool autofill_on_empty_values,
bool requires_caret_at_end) {
// We need to call non-const methods.
WebInputElement element(const_element);
if (!element.isEnabledFormControl() ||
element.inputType() != WebInputElement::Text ||
element.inputType() == WebInputElement::Password ||
!element.autoComplete() || element.isReadOnly()) {
return;
}
WebString name = element.nameForAutofill();
if (name.isEmpty()) // If the field has no name, then we won't have values.
return;
// Don't attempt to autofill with values that are too large.
WebString value = element.value();
if (value.length() > kMaximumTextSizeForAutoFill)
return;
if (!autofill_on_empty_values && value.isEmpty())
return;
if (requires_caret_at_end &&
(element.selectionStart() != element.selectionEnd() ||
element.selectionEnd() != static_cast<int>(value.length()))) {
return;
}
QueryAutoFillSuggestions(element, name, value);
}
void AutoFillHelper::QueryAutoFillFormData(const WebNode& node,
const WebString& value,
const WebString& label,
int unique_id,
AutoFillAction action) {
static int query_counter = 0;
autofill_query_id_ = query_counter++;
webkit_glue::FormData form;
const WebInputElement element = node.toConst<WebInputElement>();
if (!form_manager_.FindFormWithFormControlElement(
element, FormManager::REQUIRE_NONE, &form))
return;
autofill_action_ = action;
render_view_->Send(new ViewHostMsg_FillAutoFillFormData(
render_view_->routing_id(), autofill_query_id_, form, value, label,
unique_id));
}
void AutoFillHelper::SendForms(WebFrame* frame) {
// TODO(jhawkins): Use FormManager once we have strict ordering of form
// control elements in the cache.
WebKit::WebVector<WebFormElement> web_forms;
frame->forms(web_forms);
std::vector<webkit_glue::FormData> forms;
for (size_t i = 0; i < web_forms.size(); ++i) {
const WebFormElement& web_form = web_forms[i];
webkit_glue::FormData form;
if (FormManager::WebFormElementToFormData(
web_form, FormManager::REQUIRE_NONE, false, &form)) {
forms.push_back(form);
}
}
if (!forms.empty()) {
render_view_->Send(new ViewHostMsg_FormsSeen(render_view_->routing_id(),
forms));
}
}