diff options
author | isherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-01 16:09:05 +0000 |
---|---|---|
committer | isherman@chromium.org <isherman@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-11-01 16:09:05 +0000 |
commit | e7d8b51953b7d3b2b8a0aba46132305b32f3efce (patch) | |
tree | a3db6fecd5a39b84930a1b6df0e64bbba57fdff8 | |
parent | 08ef8bbb87fa722353014ad94bc9b9d4f0e96a38 (diff) | |
download | chromium_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.cc | 2 | ||||
-rw-r--r-- | chrome/renderer/form_manager.cc | 52 | ||||
-rw-r--r-- | chrome/renderer/form_manager.h | 17 | ||||
-rw-r--r-- | chrome/renderer/form_manager_browsertest.cc | 82 |
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) { |