summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorisherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-01 16:09:05 +0000
committerisherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2010-11-01 16:09:05 +0000
commite7d8b51953b7d3b2b8a0aba46132305b32f3efce (patch)
treea3db6fecd5a39b84930a1b6df0e64bbba57fdff8
parent08ef8bbb87fa722353014ad94bc9b9d4f0e96a38 (diff)
downloadchromium_src-e7d8b51953b7d3b2b8a0aba46132305b32f3efce.zip
chromium_src-e7d8b51953b7d3b2b8a0aba46132305b32f3efce.tar.gz
chromium_src-e7d8b51953b7d3b2b8a0aba46132305b32f3efce.tar.bz2
Handle selection changes due to AutoFill more carefully.
When previewing a form, select the contents of the initiating field so that typing will overwrite the field's contents. When a form is filled, move the cursor to the end of the initiating field, so that typing appends text. When a form is cleared, make sure that we restore the original value of the initiating field. BUG=58774 TEST=browser_tests --gtest_filter=FormManagerTest.*Fill*:FormManagerTest.*Preview* Review URL: http://codereview.chromium.org/4221002 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@64612 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/renderer/autofill_helper.cc2
-rw-r--r--chrome/renderer/form_manager.cc52
-rw-r--r--chrome/renderer/form_manager.h17
-rw-r--r--chrome/renderer/form_manager_browsertest.cc82
4 files changed, 130 insertions, 23 deletions
diff --git a/chrome/renderer/autofill_helper.cc b/chrome/renderer/autofill_helper.cc
index 0a6c186..2832f0d 100644
--- a/chrome/renderer/autofill_helper.cc
+++ b/chrome/renderer/autofill_helper.cc
@@ -159,7 +159,7 @@ void AutoFillHelper::FormDataFilled(int query_id,
form_manager_.FillForm(form, autofill_query_node_);
break;
case AUTOFILL_PREVIEW:
- form_manager_.PreviewForm(form);
+ form_manager_.PreviewForm(form, autofill_query_node_);
break;
default:
NOTREACHED();
diff --git a/chrome/renderer/form_manager.cc b/chrome/renderer/form_manager.cc
index 3fdd604..52bc6cf 100644
--- a/chrome/renderer/form_manager.cc
+++ b/chrome/renderer/form_manager.cc
@@ -561,7 +561,7 @@ bool FormManager::FillForm(const FormData& form, const WebNode& node) {
return true;
}
-bool FormManager::PreviewForm(const FormData& form) {
+bool FormManager::PreviewForm(const FormData& form, const WebNode& node) {
FormElement* form_element = NULL;
if (!FindCachedFormElement(form, &form_element))
return false;
@@ -569,7 +569,7 @@ bool FormManager::PreviewForm(const FormData& form) {
RequirementsMask requirements = static_cast<RequirementsMask>(
REQUIRE_AUTOCOMPLETE | REQUIRE_ENABLED | REQUIRE_EMPTY);
ForEachMatchingFormField(form_element,
- WebNode(),
+ node,
requirements,
form,
NewCallback(this, &FormManager::PreviewFormField));
@@ -594,6 +594,13 @@ bool FormManager::ClearFormWithNode(const WebNode& node) {
input_element.setValue(string16());
input_element.setAutofilled(false);
+
+ // Clearing the value in the focused node (above) can cause selection
+ // to be lost. We force selection range to restore the text cursor.
+ if (node == input_element) {
+ int length = input_element.value().length();
+ input_element.setSelectionRange(length, length);
+ }
} else if (element.formControlType() == WebString::fromUTF8("select-one")) {
WebSelectElement select_element = element.to<WebSelectElement>();
select_element.setValue(form_element->control_values[i]);
@@ -621,20 +628,30 @@ bool FormManager::ClearPreviewedFormWithNode(const WebNode& node) {
if (!input_element.isAutofilled())
continue;
- // If the user has completed the auto-fill and the values are filled in, we
- // don't want to reset the auto-filled status.
- if (!input_element.value().isEmpty())
+ // There might be unrelated elements in this form which have already been
+ // auto-filled. For example, the user might have already filled the address
+ // part of a form and now be dealing with the credit card section. We only
+ // want to reset the auto-filled status for fields that were previewed.
+ if (input_element.suggestedValue().isEmpty())
continue;
+ // Clear the suggested value. For the initiating node, also restore the
+ // original value.
input_element.setSuggestedValue(string16());
+ bool is_initiating_node = (node == input_element);
+ if (is_initiating_node) {
+ // Call |setValue()| to force the renderer to update the field's displayed
+ // value.
+ input_element.setValue(input_element.value());
+ }
input_element.setAutofilled(false);
// Clearing the suggested value in the focused node (above) can cause
// selection to be lost. We force selection range to restore the text
// cursor.
- if (node == input_element) {
- input_element.setSelectionRange(input_element.value().length(),
- input_element.value().length());
+ if (is_initiating_node) {
+ int length = input_element.value().length();
+ input_element.setSelectionRange(length, length);
}
}
@@ -770,6 +787,8 @@ void FormManager::ForEachMatchingFormField(FormElement* form,
DCHECK_EQ(data.fields[k].name(), element_name);
+ bool is_initiating_node = false;
+
// More than likely |requirements| will contain REQUIRE_AUTOCOMPLETE and/or
// REQUIRE_EMPTY, which both require text form control elements, so special-
// case this type of element.
@@ -782,11 +801,12 @@ void FormManager::ForEachMatchingFormField(FormElement* form,
if (requirements & REQUIRE_AUTOCOMPLETE && !input_element.autoComplete())
continue;
+ is_initiating_node = (input_element == node);
// Don't require the node that initiated the auto-fill process to be
// empty. The user is typing in this field and we should complete the
// value when the user selects a value to fill out.
if (requirements & REQUIRE_EMPTY &&
- input_element != node &&
+ !is_initiating_node &&
!input_element.value().isEmpty())
continue;
}
@@ -794,7 +814,7 @@ void FormManager::ForEachMatchingFormField(FormElement* form,
if (requirements & REQUIRE_ENABLED && !element->isEnabled())
continue;
- callback->Run(element, &data.fields[k]);
+ callback->Run(element, &data.fields[k], is_initiating_node);
// We found a matching form field so move on to the next.
++j;
@@ -804,7 +824,8 @@ void FormManager::ForEachMatchingFormField(FormElement* form,
}
void FormManager::FillFormField(WebFormControlElement* field,
- const FormField* data) {
+ const FormField* data,
+ bool is_initiating_node) {
// Nothing to fill.
if (data->value().empty())
return;
@@ -816,6 +837,10 @@ void FormManager::FillFormField(WebFormControlElement* field,
// returns the default maxlength value.
input_element.setValue(data->value().substr(0, input_element.maxLength()));
input_element.setAutofilled(true);
+ if (is_initiating_node) {
+ int length = input_element.value().length();
+ input_element.setSelectionRange(length, length);
+ }
} else if (field->formControlType() == WebString::fromUTF8("select-one")) {
WebSelectElement select_element = field->to<WebSelectElement>();
select_element.setValue(data->value());
@@ -823,7 +848,8 @@ void FormManager::FillFormField(WebFormControlElement* field,
}
void FormManager::PreviewFormField(WebFormControlElement* field,
- const FormField* data) {
+ const FormField* data,
+ bool is_initiating_node) {
// Nothing to preview.
if (data->value().empty())
return;
@@ -839,4 +865,6 @@ void FormManager::PreviewFormField(WebFormControlElement* field,
input_element.setSuggestedValue(
data->value().substr(0, input_element.maxLength()));
input_element.setAutofilled(true);
+ if (is_initiating_node)
+ input_element.setSelectionRange(0, input_element.suggestedValue().length());
}
diff --git a/chrome/renderer/form_manager.h b/chrome/renderer/form_manager.h
index 1ae64dd..b50fc55 100644
--- a/chrome/renderer/form_manager.h
+++ b/chrome/renderer/form_manager.h
@@ -92,8 +92,10 @@ class FormManager {
// store multiple forms with the same names from different frames.
bool FillForm(const webkit_glue::FormData& form, const WebKit::WebNode& node);
- // Previews the form represented by |form|. Same conditions as FillForm.
- bool PreviewForm(const webkit_glue::FormData& form);
+ // Previews the form represented by |form|. |node| is the form control element
+ // that initiated the preview process. Same conditions as FillForm.
+ bool PreviewForm(const webkit_glue::FormData& form,
+ const WebKit::WebNode &node);
// Clears the values of all input elements in the form that contains |node|.
// Returns false if the form is not found.
@@ -123,8 +125,9 @@ class FormManager {
typedef std::vector<FormElement*> FormElementList;
// The callback type used by ForEachMatchingFormField().
- typedef Callback2<WebKit::WebFormControlElement*,
- const webkit_glue::FormField*>::Type Callback;
+ typedef Callback3<WebKit::WebFormControlElement*,
+ const webkit_glue::FormField*,
+ bool>::Type Callback;
// Infers corresponding label for |element| from surrounding context in the
// DOM. Contents of preceding <p> tag or preceding text element found in
@@ -155,13 +158,15 @@ class FormManager {
// value in |data|. This method also sets the autofill attribute, causing the
// background to be yellow.
void FillFormField(WebKit::WebFormControlElement* field,
- const webkit_glue::FormField* data);
+ const webkit_glue::FormField* data,
+ bool is_initiating_node);
// A ForEachMatchingFormField() callback that sets |field|'s placeholder value
// using the value in |data|, causing the test to be greyed-out. This method
// also sets the autofill attribute, causing the background to be yellow.
void PreviewFormField(WebKit::WebFormControlElement* field,
- const webkit_glue::FormField* data);
+ const webkit_glue::FormField* data,
+ bool is_initiating_node);
// The cached FormElement objects.
FormElementList form_elements_;
diff --git a/chrome/renderer/form_manager_browsertest.cc b/chrome/renderer/form_manager_browsertest.cc
index b2709b3..7e6bc6a 100644
--- a/chrome/renderer/form_manager_browsertest.cc
+++ b/chrome/renderer/form_manager_browsertest.cc
@@ -467,7 +467,7 @@ TEST_F(FormManagerTest, FillForm) {
form.fields[3].set_value(ASCIIToUTF16("Beta"));
form.fields[4].set_value(ASCIIToUTF16("Gamma"));
form.fields[5].set_value(ASCIIToUTF16("Delta"));
- EXPECT_TRUE(form_manager.FillForm(form, WebNode()));
+ EXPECT_TRUE(form_manager.FillForm(form, input_element));
// Verify the filled elements.
WebDocument document = web_frame->document();
@@ -475,6 +475,8 @@ TEST_F(FormManagerTest, FillForm) {
document.getElementById("firstname").to<WebInputElement>();
EXPECT_TRUE(firstname.isAutofilled());
EXPECT_EQ(ASCIIToUTF16("Wyatt"), firstname.value());
+ EXPECT_EQ(5, firstname.selectionStart());
+ EXPECT_EQ(5, firstname.selectionEnd());
WebInputElement lastname =
document.getElementById("lastname").to<WebInputElement>();
@@ -592,7 +594,7 @@ TEST_F(FormManagerTest, PreviewForm) {
form.fields[3].set_value(ASCIIToUTF16("Beta"));
form.fields[4].set_value(ASCIIToUTF16("Gamma"));
form.fields[5].set_value(ASCIIToUTF16("Delta"));
- EXPECT_TRUE(form_manager.PreviewForm(form));
+ EXPECT_TRUE(form_manager.PreviewForm(form, input_element));
// Verify the previewed elements.
WebDocument document = web_frame->document();
@@ -600,6 +602,8 @@ TEST_F(FormManagerTest, PreviewForm) {
document.getElementById("firstname").to<WebInputElement>();
EXPECT_TRUE(firstname.isAutofilled());
EXPECT_EQ(ASCIIToUTF16("Wyatt"), firstname.suggestedValue());
+ EXPECT_EQ(0, firstname.selectionStart());
+ EXPECT_EQ(5, firstname.selectionEnd());
WebInputElement lastname =
document.getElementById("lastname").to<WebInputElement>();
@@ -2320,6 +2324,10 @@ TEST_F(FormManagerTest, FillFormNonEmptyField) {
ASCIIToUTF16("submit"),
0),
fields2[2]);
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(5, input_element.selectionStart());
+ EXPECT_EQ(5, input_element.selectionEnd());
}
TEST_F(FormManagerTest, ClearFormWithNode) {
@@ -2406,6 +2414,10 @@ TEST_F(FormManagerTest, ClearFormWithNode) {
string16(),
ASCIIToUTF16("submit"),
0)));
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(0, firstname.selectionStart());
+ EXPECT_EQ(0, firstname.selectionEnd());
}
TEST_F(FormManagerTest, ClearFormWithNodeContainingSelectOne) {
@@ -2483,6 +2495,10 @@ TEST_F(FormManagerTest, ClearFormWithNodeContainingSelectOne) {
string16(),
ASCIIToUTF16("submit"),
0)));
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(0, firstname.selectionStart());
+ EXPECT_EQ(0, firstname.selectionEnd());
}
TEST_F(FormManagerTest, ClearPreviewedFormWithNode) {
@@ -2520,9 +2536,9 @@ TEST_F(FormManagerTest, ClearPreviewedFormWithNode) {
email.setSuggestedValue(ASCIIToUTF16("wyatt@earp.com"));
// Clear the previewed fields.
- EXPECT_TRUE(form_manager.ClearPreviewedFormWithNode(firstname));
+ EXPECT_TRUE(form_manager.ClearPreviewedFormWithNode(lastname));
- // Fields with non-empty values are not modified.
+ // Fields with empty suggestions suggestions are not modified.
EXPECT_EQ(ASCIIToUTF16("Wyatt"), firstname.value());
EXPECT_TRUE(firstname.suggestedValue().isEmpty());
EXPECT_TRUE(firstname.isAutofilled());
@@ -2534,6 +2550,64 @@ TEST_F(FormManagerTest, ClearPreviewedFormWithNode) {
EXPECT_TRUE(email.value().isEmpty());
EXPECT_TRUE(email.suggestedValue().isEmpty());
EXPECT_FALSE(email.isAutofilled());
+
+ // Verify that the cursor position has been updated.
+ EXPECT_EQ(0, lastname.selectionStart());
+ EXPECT_EQ(0, lastname.selectionEnd());
+}
+
+TEST_F(FormManagerTest, ClearPreviewedFormWithNonEmptyInitiatingNode) {
+ LoadHTML("<FORM name=\"TestForm\" action=\"http://buh.com\" method=\"post\">"
+ " <INPUT type=\"text\" id=\"firstname\" value=\"W\"/>"
+ " <INPUT type=\"text\" id=\"lastname\"/>"
+ " <INPUT type=\"text\" id=\"email\"/>"
+ " <INPUT type=\"submit\" value=\"Send\"/>"
+ "</FORM>");
+
+ WebFrame* web_frame = GetMainFrame();
+ ASSERT_NE(static_cast<WebFrame*>(NULL), web_frame);
+
+ FormManager form_manager;
+ form_manager.ExtractForms(web_frame);
+
+ // Verify that we have the form.
+ std::vector<FormData> forms;
+ form_manager.GetFormsInFrame(web_frame, FormManager::REQUIRE_NONE, &forms);
+ ASSERT_EQ(1U, forms.size());
+
+ // Set the auto-filled attribute.
+ WebInputElement firstname =
+ web_frame->document().getElementById("firstname").to<WebInputElement>();
+ firstname.setAutofilled(true);
+ WebInputElement lastname =
+ web_frame->document().getElementById("lastname").to<WebInputElement>();
+ lastname.setAutofilled(true);
+ WebInputElement email =
+ web_frame->document().getElementById("email").to<WebInputElement>();
+ email.setAutofilled(true);
+
+ // Set the suggested values on all of the elements.
+ firstname.setSuggestedValue(ASCIIToUTF16("Wyatt"));
+ lastname.setSuggestedValue(ASCIIToUTF16("Earp"));
+ email.setSuggestedValue(ASCIIToUTF16("wyatt@earp.com"));
+
+ // Clear the previewed fields.
+ EXPECT_TRUE(form_manager.ClearPreviewedFormWithNode(firstname));
+
+ // Fields with non-empty values are restored.
+ EXPECT_EQ(ASCIIToUTF16("W"), firstname.value());
+ EXPECT_TRUE(firstname.suggestedValue().isEmpty());
+ EXPECT_FALSE(firstname.isAutofilled());
+ EXPECT_EQ(1, firstname.selectionStart());
+ EXPECT_EQ(1, firstname.selectionEnd());
+
+ // Verify the previewed fields are cleared.
+ EXPECT_TRUE(lastname.value().isEmpty());
+ EXPECT_TRUE(lastname.suggestedValue().isEmpty());
+ EXPECT_FALSE(lastname.isAutofilled());
+ EXPECT_TRUE(email.value().isEmpty());
+ EXPECT_TRUE(email.suggestedValue().isEmpty());
+ EXPECT_FALSE(email.isAutofilled());
}
TEST_F(FormManagerTest, FormWithNodeIsAutoFilled) {