diff options
author | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-13 22:26:30 +0000 |
---|---|---|
committer | jhawkins@chromium.org <jhawkins@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-06-13 22:26:30 +0000 |
commit | 631ec0519a7a783dc3061076e828ecbd06401e66 (patch) | |
tree | ccff5fe6b2b21ee9a5d6c73c32025581b76e9455 /chrome | |
parent | 010bf046498b918d839fdd56fca9d47f3b9171e2 (diff) | |
download | chromium_src-631ec0519a7a783dc3061076e828ecbd06401e66.zip chromium_src-631ec0519a7a783dc3061076e828ecbd06401e66.tar.gz chromium_src-631ec0519a7a783dc3061076e828ecbd06401e66.tar.bz2 |
AutoFill: Implement the 'Clear form' menu item in the suggestions popup.
BUG=44616
TEST=FormManagerTest.*
Review URL: http://codereview.chromium.org/2752010
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@49658 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome')
-rw-r--r-- | chrome/app/generated_resources.grd | 3 | ||||
-rw-r--r-- | chrome/renderer/form_manager.cc | 78 | ||||
-rw-r--r-- | chrome/renderer/form_manager.h | 16 | ||||
-rw-r--r-- | chrome/renderer/form_manager_unittest.cc | 214 | ||||
-rwxr-xr-x | chrome/renderer/render_view.cc | 25 |
5 files changed, 307 insertions, 29 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 8dc89fa..2e0827a 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -5605,6 +5605,9 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_AUTOFILL_OPTIONS_MENU_ITEM" desc="The entry in the suggestions dropdown that opens the AutoFill dialog."> AutoFill Options... </message> + <message name="IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM" desc="The entry in the suggestions dropdown that clears an auto-filled form."> + Clear form + </message> <message name="IDS_AUTOFILL_DIALOG_TITLE" desc="The title of the AutoFill dialog."> AutoFill Profiles </message> diff --git a/chrome/renderer/form_manager.cc b/chrome/renderer/form_manager.cc index 3868582..7353b1a 100644 --- a/chrome/renderer/form_manager.cc +++ b/chrome/renderer/form_manager.cc @@ -237,7 +237,8 @@ void FormManager::WebFormControlElementToFormField( // TODO(jhawkins): In WebKit, move value() and setValue() to // WebFormControlElement. string16 value; - if (element.formControlType() == WebString::fromUTF8("text")) { + if (element.formControlType() == WebString::fromUTF8("text") || + element.formControlType() == WebString::fromUTF8("hidden")) { const WebInputElement& input_element = element.toConst<WebInputElement>(); value = input_element.value(); @@ -546,10 +547,33 @@ bool FormManager::PreviewForm(const FormData& form) { return true; } -void FormManager::ClearPreviewedForm(const FormData& form) { +bool FormManager::ClearFormWithNode(const WebKit::WebNode& node) { + FormElement* form_element = NULL; + if (!FindCachedFormElementWithNode(node, &form_element)) + return false; + + for (size_t i = 0; i < form_element->control_elements.size(); ++i) { + WebFormControlElement element = form_element->control_elements[i]; + if (element.formControlType() != WebString::fromUTF8("text")) + continue; + + WebInputElement input_element = element.to<WebInputElement>(); + + // We don't modify the value of disabled fields. + if (!input_element.isEnabled()) + continue; + + input_element.setValue(string16()); + input_element.setAutofilled(false); + } + + return true; +} + +bool FormManager::ClearPreviewedForm(const FormData& form) { FormElement* form_element = NULL; if (!FindCachedFormElement(form, &form_element)) - return; + return false; for (size_t i = 0; i < form_element->control_elements.size(); ++i) { WebFormControlElement* element = &form_element->control_elements[i]; @@ -564,9 +588,16 @@ void FormManager::ClearPreviewedForm(const FormData& form) { 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()) + continue; + input_element.setPlaceholder(string16()); input_element.setAutofilled(false); } + + return true; } void FormManager::Reset() { @@ -585,6 +616,24 @@ void FormManager::ResetFrame(const WebFrame* frame) { } } +bool FormManager::FormWithNodeIsAutoFilled(const WebKit::WebNode& node) { + FormElement* form_element = NULL; + if (!FindCachedFormElementWithNode(node, &form_element)) + return false; + + for (size_t i = 0; i < form_element->control_elements.size(); ++i) { + WebFormControlElement element = form_element->control_elements[i]; + if (element.formControlType() != WebString::fromUTF8("text")) + continue; + + const WebInputElement& input_element = element.to<WebInputElement>(); + if (input_element.isAutofilled()) + return true; + } + + return false; +} + // static bool FormManager::FormElementToFormData(const WebFrame* frame, const FormElement* form_element, @@ -605,7 +654,6 @@ bool FormManager::FormElementToFormData(const WebFrame* frame, if (!form->action.is_valid()) form->action = GURL(form_element->form_element.action()); - // Form elements loop. for (std::vector<WebFormControlElement>::const_iterator element_iter = form_element->control_elements.begin(); element_iter != form_element->control_elements.end(); ++element_iter) { @@ -648,6 +696,28 @@ string16 FormManager::InferLabelForElement( return inferred_label; } +bool FormManager::FindCachedFormElementWithNode(const WebKit::WebNode& node, + FormElement** form_element) { + for (WebFrameFormElementMap::const_iterator frame_iter = + form_elements_map_.begin(); + frame_iter != form_elements_map_.end(); ++frame_iter) { + for (std::vector<FormElement*>::const_iterator form_iter = + frame_iter->second.begin(); + form_iter != frame_iter->second.end(); ++form_iter) { + for (std::vector<WebKit::WebFormControlElement>::const_iterator iter = + (*form_iter)->control_elements.begin(); + iter != (*form_iter)->control_elements.end(); ++iter) { + if (*iter == node) { + *form_element = *form_iter; + return true; + } + } + } + } + + return false; +} + bool FormManager::FindCachedFormElement(const FormData& form, FormElement** form_element) { for (WebFrameFormElementMap::iterator iter = form_elements_map_.begin(); diff --git a/chrome/renderer/form_manager.h b/chrome/renderer/form_manager.h index 4d6aa5c..bfc639f 100644 --- a/chrome/renderer/form_manager.h +++ b/chrome/renderer/form_manager.h @@ -96,9 +96,14 @@ class FormManager { // Previews the form represented by |form|. Same conditions as FillForm. bool PreviewForm(const webkit_glue::FormData& form); + // Clears the values of all input elements in the form that contains |node|. + // Returns false if the form is not found. + bool ClearFormWithNode(const WebKit::WebNode& node); + // Clears the placeholder values and the auto-filled background for any fields - // in |form| that have been previewed. - void ClearPreviewedForm(const webkit_glue::FormData& form); + // in |form| that have been previewed. Returns false if the form is not + // found. + bool ClearPreviewedForm(const webkit_glue::FormData& form); // Resets the stored set of forms. void Reset(); @@ -106,6 +111,9 @@ class FormManager { // Resets the forms for the specified |frame|. void ResetFrame(const WebKit::WebFrame* frame); + // Returns true if |form| has any auto-filled fields. + bool FormWithNodeIsAutoFilled(const WebKit::WebNode& node); + private: // Stores the WebFormElement and the form control elements for a form. struct FormElement { @@ -136,6 +144,10 @@ class FormManager { static string16 InferLabelForElement( const WebKit::WebFormControlElement& element); + // Finds the cached FormElement that contains |node|. + bool FindCachedFormElementWithNode(const WebKit::WebNode& node, + FormElement** form_element); + // Uses the data in |form| to find the cached FormElement. bool FindCachedFormElement(const webkit_glue::FormData& form, FormElement** form_element); diff --git a/chrome/renderer/form_manager_unittest.cc b/chrome/renderer/form_manager_unittest.cc index 1731f47..e5b9ffd 100644 --- a/chrome/renderer/form_manager_unittest.cc +++ b/chrome/renderer/form_manager_unittest.cc @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/string_util.h" #include "chrome/renderer/form_manager.h" #include "chrome/test/render_view_test.h" #include "testing/gtest/include/gtest/gtest.h" @@ -34,6 +35,7 @@ TEST_F(FormManagerTest, WebFormElementToFormData) { LoadHTML("<FORM name=\"TestForm\" action=\"http://cnn.com\" method=\"post\">" " <INPUT type=\"text\" id=\"firstname\" value=\"John\"/>" " <INPUT type=\"text\" id=\"lastname\" value=\"Smith\"/>" + " <INPUT type=\"hidden\" id=\"notvisible\" value=\"apple\"/>" " <INPUT type=\"submit\" name=\"reply-send\" value=\"Send\"/>" "</FORM>"); @@ -54,25 +56,31 @@ TEST_F(FormManagerTest, WebFormElementToFormData) { EXPECT_EQ(GURL("http://cnn.com"), form.action); const std::vector<FormField>& fields = form.fields; - ASSERT_EQ(3U, fields.size()); - EXPECT_EQ(FormField(string16(), - ASCIIToUTF16("firstname"), - ASCIIToUTF16("John"), - ASCIIToUTF16("text"), - 20), - fields[0]); - EXPECT_EQ(FormField(string16(), - ASCIIToUTF16("lastname"), - ASCIIToUTF16("Smith"), - ASCIIToUTF16("text"), - 20), - fields[1]); - EXPECT_EQ(FormField(string16(), - ASCIIToUTF16("reply-send"), - ASCIIToUTF16("Send"), - ASCIIToUTF16("submit"), - 0), - fields[2]); + ASSERT_EQ(4U, fields.size()); + EXPECT_TRUE(fields[0].StrictlyEqualsHack( + FormField(string16(), + ASCIIToUTF16("firstname"), + ASCIIToUTF16("John"), + ASCIIToUTF16("text"), + 20))); + EXPECT_TRUE(fields[1].StrictlyEqualsHack( + FormField(string16(), + ASCIIToUTF16("lastname"), + ASCIIToUTF16("Smith"), + ASCIIToUTF16("text"), + 20))); + EXPECT_TRUE(fields[2].StrictlyEqualsHack( + FormField(string16(), + ASCIIToUTF16("notvisible"), + ASCIIToUTF16("apple"), + ASCIIToUTF16("hidden"), + 0))); + EXPECT_TRUE(fields[3].StrictlyEqualsHack( + FormField(string16(), + ASCIIToUTF16("reply-send"), + string16(), + ASCIIToUTF16("submit"), + 0))); } TEST_F(FormManagerTest, ExtractForms) { @@ -2164,4 +2172,172 @@ TEST_F(FormManagerTest, FillFormNonEmptyField) { fields2[2]); } +TEST_F(FormManagerTest, ClearFormWithNode) { + LoadHTML( + "<FORM name=\"TestForm\" action=\"http://buh.com\" method=\"post\">" + " <INPUT type=\"text\" id=\"firstname\" value=\"Wyatt\"/>" + " <INPUT type=\"text\" id=\"lastname\" value=\"Earp\"/>" + " <INPUT type=\"text\" autocomplete=\"off\" id=\"noAC\" value=\"one\"/>" + " <INPUT type=\"text\" id=\"notenabled\" disabled=\"disabled\">" + " <INPUT type=\"hidden\" id=\"notvisible\" value=\"apple\">" + " <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.GetForms(FormManager::REQUIRE_NONE, &forms); + ASSERT_EQ(1U, forms.size()); + + // Set the auto-filled attribute on the firstname element. + WebInputElement firstname = + web_frame->document().getElementById("firstname").to<WebInputElement>(); + firstname.setAutofilled(true); + + // Set the value of the disabled attribute. + WebInputElement notenabled = + web_frame->document().getElementById("notenabled").to<WebInputElement>(); + notenabled.setValue(WebString::fromUTF8("no clear")); + + // Clear the form. + EXPECT_TRUE(form_manager.ClearFormWithNode(firstname)); + + // Verify that the auto-filled attribute has been turned off. + EXPECT_FALSE(firstname.isAutofilled()); + + // Verify the form is cleared. + FormData form2; + EXPECT_TRUE(form_manager.FindFormWithFormControlElement( + firstname, FormManager::REQUIRE_NONE, &form2)); + EXPECT_EQ(ASCIIToUTF16("TestForm"), form2.name); + EXPECT_EQ(GURL(web_frame->url()), form2.origin); + EXPECT_EQ(GURL("http://buh.com"), form2.action); + + const std::vector<FormField>& fields2 = form2.fields; + ASSERT_EQ(6U, fields2.size()); + EXPECT_TRUE(fields2[0].StrictlyEqualsHack( + FormField(string16(), + ASCIIToUTF16("firstname"), + string16(), + ASCIIToUTF16("text"), + 20))); + EXPECT_TRUE(fields2[1].StrictlyEqualsHack( + FormField(string16(), + ASCIIToUTF16("lastname"), + string16(), + ASCIIToUTF16("text"), + 20))); + EXPECT_TRUE(fields2[2].StrictlyEqualsHack( + FormField(string16(), + ASCIIToUTF16("noAC"), + string16(), + ASCIIToUTF16("text"), + 20))); + EXPECT_TRUE(fields2[3].StrictlyEqualsHack( + FormField(string16(), + ASCIIToUTF16("notenabled"), + ASCIIToUTF16("no clear"), + ASCIIToUTF16("text"), + 20))); + EXPECT_TRUE(fields2[4].StrictlyEqualsHack( + FormField(string16(), + ASCIIToUTF16("notvisible"), + ASCIIToUTF16("apple"), + ASCIIToUTF16("hidden"), + 0))); + EXPECT_TRUE(fields2[5].StrictlyEqualsHack( + FormField(string16(), + string16(), + string16(), + ASCIIToUTF16("submit"), + 0))); +} + +TEST_F(FormManagerTest, ClearPreviewedForm) { + LoadHTML("<FORM name=\"TestForm\" action=\"http://buh.com\" method=\"post\">" + " <INPUT type=\"text\" id=\"firstname\" value=\"Wyatt\"/>" + " <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.GetForms(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 placeholder values on two of the elements. + lastname.setPlaceholder(ASCIIToUTF16("Earp")); + email.setPlaceholder(ASCIIToUTF16("wyatt@earp.com")); + + // Clear the previewed fields. + EXPECT_TRUE(form_manager.ClearPreviewedForm(forms[0])); + + // Fields with non-empty values are not modified. + EXPECT_EQ(ASCIIToUTF16("Wyatt"), firstname.value()); + EXPECT_TRUE(firstname.placeholder().isEmpty()); + EXPECT_TRUE(firstname.isAutofilled()); + + // Verify the previewed fields are cleared. + EXPECT_TRUE(lastname.value().isEmpty()); + EXPECT_TRUE(lastname.placeholder().isEmpty()); + EXPECT_FALSE(lastname.isAutofilled()); + EXPECT_TRUE(email.value().isEmpty()); + EXPECT_TRUE(email.placeholder().isEmpty()); + EXPECT_FALSE(email.isAutofilled()); +} + +TEST_F(FormManagerTest, FormWithNodeIsAutoFilled) { + LoadHTML("<FORM name=\"TestForm\" action=\"http://buh.com\" method=\"post\">" + " <INPUT type=\"text\" id=\"firstname\" value=\"Wyatt\"/>" + " <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.GetForms(FormManager::REQUIRE_NONE, &forms); + ASSERT_EQ(1U, forms.size()); + + WebInputElement firstname = + web_frame->document().getElementById("firstname").to<WebInputElement>(); + + // Auto-filled attribute not set yet. + EXPECT_FALSE(form_manager.FormWithNodeIsAutoFilled(firstname)); + + // Set the auto-filled attribute. + firstname.setAutofilled(true); + + EXPECT_TRUE(form_manager.FormWithNodeIsAutoFilled(firstname)); +} + } // namespace diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index 78f1926..29f0f1a 100755 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -1518,11 +1518,22 @@ void RenderView::OnAutoFillSuggestionsReturned( if (webview() && query_id == autofill_query_id_) { std::vector<string16> v(values); std::vector<string16> l(labels); + int separator_index = v.size(); + + // The form has been auto-filled, so give the user the chance to clear the + // form. + if (form_manager_.FormWithNodeIsAutoFilled(autofill_query_node_)) { + v.push_back(l10n_util::GetStringUTF16( + IDS_AUTOFILL_CLEAR_FORM_MENU_ITEM)); + l.push_back(string16()); + } + + // Append the 'AutoFill Options...' menu item. v.push_back(l10n_util::GetStringUTF16(IDS_AUTOFILL_OPTIONS_MENU_ITEM)); l.push_back(string16()); suggestions_count_ = v.size(); webview()->applyAutoFillSuggestions( - autofill_query_node_, v, l, v.size() - 1); + autofill_query_node_, v, l, separator_index); } } @@ -2112,11 +2123,17 @@ void RenderView::didAcceptAutoFillSuggestion(const WebKit::WebNode& node, unsigned index) { DCHECK_NE(0U, suggestions_count_); - // User selected 'AutoFill Options...'. - if (index == suggestions_count_ - 1) + if (index == suggestions_count_ - 1) { + // User selected 'AutoFill Options...'. Send(new ViewHostMsg_ShowAutoFillDialog(routing_id_)); - else + } else if (form_manager_.FormWithNodeIsAutoFilled(node) && + index == suggestions_count_ - 2) { + // The form has been auto-filled, so give the user the chance to clear the + // form. + form_manager_.ClearFormWithNode(node); + } else { QueryAutoFillFormData(node, value, label, AUTOFILL_FILL); + } suggestions_count_ = 0; } |