diff options
-rw-r--r-- | chrome/renderer/form_manager.cc | 111 | ||||
-rw-r--r-- | chrome/renderer/form_manager.h | 27 | ||||
-rw-r--r-- | chrome/renderer/form_manager_unittest.cc | 135 | ||||
-rwxr-xr-x | chrome/renderer/render_view.cc | 57 | ||||
-rw-r--r-- | chrome/renderer/render_view.h | 23 |
5 files changed, 312 insertions, 41 deletions
diff --git a/chrome/renderer/form_manager.cc b/chrome/renderer/form_manager.cc index 9f2b785..6cc4e17 100644 --- a/chrome/renderer/form_manager.cc +++ b/chrome/renderer/form_manager.cc @@ -316,7 +316,7 @@ bool FormManager::WebFormElementToFormData(const WebFormElement& element, continue; } - if (requirements & REQUIRE_ELEMENTS_ENABLED && !control_element.isEnabled()) + if (requirements & REQUIRE_ENABLED && !control_element.isEnabled()) continue; // Create a new FormField, fill it out and map it to the field's name. @@ -519,14 +519,56 @@ bool FormManager::FillForm(const FormData& form) { if (!FindCachedFormElement(form, &form_element)) return false; - ForEachMatchingFormField( - form_element, form, NewCallback(this, &FormManager::FillFormField)); + RequirementsMask requirements = static_cast<RequirementsMask>( + REQUIRE_AUTOCOMPLETE | REQUIRE_ENABLED | REQUIRE_EMPTY); + ForEachMatchingFormField(form_element, + requirements, + form, + NewCallback(this, &FormManager::FillFormField)); return true; } -void FormManager::FillForms(const std::vector<webkit_glue::FormData>& forms) { - for (std::vector<webkit_glue::FormData>::const_iterator iter = forms.begin(); +bool FormManager::PreviewForm(const FormData& form) { + FormElement* form_element = NULL; + if (!FindCachedFormElement(form, &form_element)) + return false; + + RequirementsMask requirements = static_cast<RequirementsMask>( + REQUIRE_AUTOCOMPLETE | REQUIRE_ENABLED | REQUIRE_EMPTY); + ForEachMatchingFormField(form_element, + requirements, + form, + NewCallback(this, &FormManager::PreviewFormField)); + + return true; +} + +void FormManager::ClearPreviewedForm(const FormData& form) { + FormElement* form_element = NULL; + if (!FindCachedFormElement(form, &form_element)) + return; + + for (size_t i = 0; i < form_element->control_elements.size(); ++i) { + WebFormControlElement* element = &form_element->control_elements[i]; + + // Only input elements can be previewed. + if (element->formControlType() != WebString::fromUTF8("text")) + continue; + + // If the input element has not been auto-filled, FormManager has not + // previewed this field, so we have nothing to reset. + WebInputElement input_element = element->to<WebInputElement>(); + if (!input_element.isAutofilled()) + continue; + + input_element.setPlaceholder(string16()); + input_element.setAutofilled(false); + } +} + +void FormManager::FillForms(const std::vector<FormData>& forms) { + for (std::vector<FormData>::const_iterator iter = forms.begin(); iter != forms.end(); ++iter) { FillForm(*iter); } @@ -582,7 +624,7 @@ bool FormManager::FormElementToFormData(const WebFrame* frame, continue; } - if (requirements & REQUIRE_ELEMENTS_ENABLED && !control_element.isEnabled()) + if (requirements & REQUIRE_ENABLED && !control_element.isEnabled()) continue; FormField field; @@ -639,8 +681,10 @@ bool FormManager::FindCachedFormElement(const FormData& form, return false; } -void FormManager::ForEachMatchingFormField( - FormElement* form, const FormData& data, Callback* callback) { +void FormManager::ForEachMatchingFormField(FormElement* form, + RequirementsMask requirements, + const FormData& data, + Callback* callback) { // It's possible that the site has injected fields into the form after the // page has loaded, so we can't assert that the size of the cached control // elements is equal to the size of the fields in |form|. Fortunately, the @@ -666,6 +710,26 @@ void FormManager::ForEachMatchingFormField( continue; DCHECK_EQ(data.fields[k].name(), element_name); + + // 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. + if (element->formControlType() == WebString::fromUTF8("text")) { + const WebInputElement& input_element = + element->toConst<WebInputElement>(); + + // TODO(jhawkins): WebKit currently doesn't handle the autocomplete + // attribute for select control elements, but it probably should. + if (requirements & REQUIRE_AUTOCOMPLETE && !input_element.autoComplete()) + continue; + + if (requirements & REQUIRE_EMPTY && !input_element.value().isEmpty()) + continue; + } + + if (requirements & REQUIRE_ENABLED && !element->isEnabled()) + continue; + callback->Run(element, &data.fields[k]); // We found a matching form field so move on to the next. @@ -684,18 +748,6 @@ void FormManager::FillFormField(WebKit::WebFormControlElement* field, if (field->formControlType() == WebString::fromUTF8("text")) { WebInputElement input_element = field->to<WebInputElement>(); - // Don't auto-fill a disabled field. - if (!input_element.isEnabledFormControl()) - return; - - // Don't auto-fill a field with autocomplete=off. - if (!input_element.autoComplete()) - return; - - // Don't overwrite a pre-existing value in the field. - if (!input_element.value().isEmpty()) - return; - // If the maxlength attribute contains a negative value, maxLength() // returns the default maxlength value. input_element.setValue(data->value().substr(0, input_element.maxLength())); @@ -705,3 +757,22 @@ void FormManager::FillFormField(WebKit::WebFormControlElement* field, select_element.setValue(data->value()); } } + +void FormManager::PreviewFormField(WebKit::WebFormControlElement* field, + const FormField* data) { + // Nothing to preview. + if (data->value().empty()) + return; + + // Only preview input fields. + if (field->formControlType() != WebString::fromUTF8("text")) + return; + + WebInputElement input_element = field->to<WebInputElement>(); + + // If the maxlength attribute contains a negative value, maxLength() + // returns the default maxlength value. + input_element.setPlaceholder( + data->value().substr(0, input_element.maxLength())); + input_element.setAutofilled(true); +} diff --git a/chrome/renderer/form_manager.h b/chrome/renderer/form_manager.h index 57ed5af..ad4dee7 100644 --- a/chrome/renderer/form_manager.h +++ b/chrome/renderer/form_manager.h @@ -29,7 +29,8 @@ class FormManager { enum RequirementsMask { REQUIRE_NONE = 0x0, // No requirements. REQUIRE_AUTOCOMPLETE = 0x1, // Require that autocomplete != off. - REQUIRE_ELEMENTS_ENABLED = 0x2 // Require that disabled attribute is off. + REQUIRE_ENABLED = 0x2, // Require that disabled attribute is off. + REQUIRE_EMPTY = 0x4, // Require that the fields are empty. }; FormManager(); @@ -91,6 +92,13 @@ class FormManager { // store multiple forms with the same names from different frames. bool FillForm(const webkit_glue::FormData& form); + // Previews the form represented by |form|. Same conditions as FillForm. + bool PreviewForm(const webkit_glue::FormData& form); + + // 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); + // Fills all of the forms in the cache with form data from |forms|. void FillForms(const std::vector<webkit_glue::FormData>& forms); @@ -134,18 +142,27 @@ class FormManager { bool FindCachedFormElement(const webkit_glue::FormData& form, FormElement** form_element); - // For each field in |form| that matches the corresponding field in the cached - // FormElement, |callback| is called with the actual WebFormControlElement and - // the FormField data from |form|. This method owns |callback|. + // For each field in |data| that matches the corresponding field in |form| + // and meets the |requirements|, |callback| is called with the actual + // WebFormControlElement and the FormField data from |form|. This method owns + // |callback|. void ForEachMatchingFormField(FormElement* form, + RequirementsMask requirements, const webkit_glue::FormData& data, Callback* callback); // A ForEachMatchingFormField() callback that sets |field|'s value using the - // value in |data|. + // 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); + // 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); + // The map of form elements. WebFrameFormElementMap form_elements_map_; diff --git a/chrome/renderer/form_manager_unittest.cc b/chrome/renderer/form_manager_unittest.cc index e267aff..4344499 100644 --- a/chrome/renderer/form_manager_unittest.cc +++ b/chrome/renderer/form_manager_unittest.cc @@ -258,7 +258,7 @@ TEST_F(FormManagerTest, GetFormsElementsEnabled) { form_manager.ExtractForms(web_frame); std::vector<FormData> forms; - form_manager.GetForms(FormManager::REQUIRE_ELEMENTS_ENABLED, &forms); + form_manager.GetForms(FormManager::REQUIRE_ENABLED, &forms); ASSERT_EQ(1U, forms.size()); const FormData& form = forms[0]; @@ -426,35 +426,164 @@ TEST_F(FormManagerTest, FillForm) { WebDocument document = web_frame->document(); WebInputElement firstname = document.getElementById("firstname").to<WebInputElement>(); - // TODO(jhawkins): Check firstname.isAutofilled() once support has been added - // in WebKit. + EXPECT_TRUE(firstname.isAutofilled()); EXPECT_EQ(ASCIIToUTF16("Wyatt"), firstname.value()); WebInputElement lastname = document.getElementById("lastname").to<WebInputElement>(); + EXPECT_TRUE(lastname.isAutofilled()); EXPECT_EQ(ASCIIToUTF16("Earp"), lastname.value()); // Hidden fields are not filled. WebInputElement imhidden = document.getElementById("imhidden").to<WebInputElement>(); + EXPECT_FALSE(imhidden.isAutofilled()); EXPECT_TRUE(imhidden.value().isEmpty()); // Non-empty fields are not filled. WebInputElement notempty = document.getElementById("notempty").to<WebInputElement>(); + EXPECT_FALSE(notempty.isAutofilled()); EXPECT_EQ(ASCIIToUTF16("Hi"), notempty.value()); // autocomplete=off fields are not filled. WebInputElement noautocomplete = document.getElementById("noautocomplete").to<WebInputElement>(); + EXPECT_FALSE(noautocomplete.isAutofilled()); EXPECT_TRUE(noautocomplete.value().isEmpty()); // Disabled fields are not filled. WebInputElement notenabled = document.getElementById("notenabled").to<WebInputElement>(); + EXPECT_FALSE(notenabled.isAutofilled()); EXPECT_TRUE(notenabled.value().isEmpty()); } +TEST_F(FormManagerTest, PreviewForm) { + LoadHTML("<FORM name=\"TestForm\" action=\"http://buh.com\" method=\"post\">" + " <INPUT type=\"text\" id=\"firstname\"/>" + " <INPUT type=\"text\" id=\"lastname\"/>" + " <INPUT type=\"hidden\" id=\"imhidden\"/>" + " <INPUT type=\"text\" id=\"notempty\" value=\"Hi\"/>" + " <INPUT type=\"text\" autocomplete=\"off\" id=\"noautocomplete\"/>" + " <INPUT type=\"text\" disabled=\"disabled\" id=\"notenabled\"/>" + " <INPUT type=\"submit\" name=\"reply-send\" 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()); + + // Get the input element we want to find. + WebElement element = web_frame->document().getElementById("firstname"); + WebInputElement input_element = element.to<WebInputElement>(); + + // Find the form that contains the input element. + FormData form; + EXPECT_TRUE(form_manager.FindFormWithFormControlElement( + input_element, FormManager::REQUIRE_NONE, &form)); + EXPECT_EQ(ASCIIToUTF16("TestForm"), form.name); + EXPECT_EQ(GURL(web_frame->url()), form.origin); + EXPECT_EQ(GURL("http://buh.com"), form.action); + + const std::vector<FormField>& fields = form.fields; + ASSERT_EQ(7U, fields.size()); + EXPECT_EQ(FormField(string16(), + ASCIIToUTF16("firstname"), + string16(), + ASCIIToUTF16("text"), + 20), + fields[0]); + EXPECT_EQ(FormField(string16(), + ASCIIToUTF16("lastname"), + string16(), + ASCIIToUTF16("text"), + 20), + fields[1]); + EXPECT_EQ(FormField(string16(), + ASCIIToUTF16("imhidden"), + string16(), + ASCIIToUTF16("hidden"), + 0), + fields[2]); + EXPECT_EQ(FormField(string16(), + ASCIIToUTF16("notempty"), + ASCIIToUTF16("Hi"), + ASCIIToUTF16("text"), + 20), + fields[3]); + EXPECT_EQ(FormField(string16(), + ASCIIToUTF16("noautocomplete"), + string16(), + ASCIIToUTF16("text"), + 20), + fields[4]); + EXPECT_EQ(FormField(string16(), + ASCIIToUTF16("notenabled"), + string16(), + ASCIIToUTF16("text"), + 20), + fields[5]); + EXPECT_EQ(FormField(string16(), + ASCIIToUTF16("reply-send"), + ASCIIToUTF16("Send"), + ASCIIToUTF16("submit"), + 0), + fields[6]); + + // Preview the form. + form.fields[0].set_value(ASCIIToUTF16("Wyatt")); + form.fields[1].set_value(ASCIIToUTF16("Earp")); + form.fields[2].set_value(ASCIIToUTF16("Alpha")); + 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)); + + // Verify the previewed elements. + WebDocument document = web_frame->document(); + WebInputElement firstname = + document.getElementById("firstname").to<WebInputElement>(); + EXPECT_TRUE(firstname.isAutofilled()); + EXPECT_EQ(ASCIIToUTF16("Wyatt"), firstname.placeholder()); + + WebInputElement lastname = + document.getElementById("lastname").to<WebInputElement>(); + EXPECT_TRUE(lastname.isAutofilled()); + EXPECT_EQ(ASCIIToUTF16("Earp"), lastname.placeholder()); + + // Hidden fields are not previewed. + WebInputElement imhidden = + document.getElementById("imhidden").to<WebInputElement>(); + EXPECT_FALSE(imhidden.isAutofilled()); + EXPECT_TRUE(imhidden.placeholder().isEmpty()); + + // Non-empty fields are not previewed. + WebInputElement notempty = + document.getElementById("notempty").to<WebInputElement>(); + EXPECT_FALSE(notempty.isAutofilled()); + EXPECT_TRUE(notempty.placeholder().isEmpty()); + + // autocomplete=off fields are not previewed. + WebInputElement noautocomplete = + document.getElementById("noautocomplete").to<WebInputElement>(); + EXPECT_FALSE(noautocomplete.isAutofilled()); + EXPECT_TRUE(noautocomplete.placeholder().isEmpty()); + + // Disabled fields are not previewed. + WebInputElement notenabled = + document.getElementById("notenabled").to<WebInputElement>(); + EXPECT_FALSE(notenabled.isAutofilled()); + EXPECT_TRUE(notenabled.placeholder().isEmpty()); +} + TEST_F(FormManagerTest, Reset) { LoadHTML("<FORM name=\"TestForm\" action=\"http://cnn.com\" method=\"post\">" " <INPUT type=\"text\" id=\"firstname\" value=\"John\"/>" diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc index d00d948..a37872e 100755 --- a/chrome/renderer/render_view.cc +++ b/chrome/renderer/render_view.cc @@ -402,7 +402,8 @@ RenderView::RenderView(RenderThreadBase* render_thread, ALLOW_THIS_IN_INITIALIZER_LIST(translate_helper_(this)), cross_origin_access_count_(0), same_origin_access_count_(0), - ALLOW_THIS_IN_INITIALIZER_LIST(pepper_delegate_(this)) { + ALLOW_THIS_IN_INITIALIZER_LIST(pepper_delegate_(this)), + autofill_action_(AUTOFILL_NONE) { ClearBlockedContentSettings(); } @@ -1505,10 +1506,17 @@ void RenderView::OnAutocompleteSuggestionsReturned( void RenderView::OnAutoFillFormDataFilled(int query_id, const webkit_glue::FormData& form) { - if (query_id != autofill_query_id_) + if (!webview() || query_id != autofill_query_id_) return; - form_manager_.FillForm(form); + DCHECK_NE(AUTOFILL_NONE, autofill_action_); + + if (autofill_action_ == AUTOFILL_FILL) + form_manager_.FillForm(form); + else if (autofill_action_ == AUTOFILL_PREVIEW) + form_manager_.PreviewForm(form); + + autofill_action_ = AUTOFILL_NONE; } void RenderView::OnAllowScriptToClose(bool script_can_close) { @@ -2030,21 +2038,26 @@ void RenderView::removeAutofillSuggestions(const WebString& name, Send(new ViewHostMsg_RemoveAutofillEntry(routing_id_, name, value)); } -void RenderView::didAcceptAutoFillSuggestion( - const WebKit::WebNode& node, - const WebKit::WebString& value, - const WebKit::WebString& label) { - static int query_counter = 0; - autofill_query_id_ = query_counter++; +void RenderView::didAcceptAutoFillSuggestion(const WebKit::WebNode& node, + const WebKit::WebString& value, + const WebKit::WebString& label) { + QueryAutoFillFormData(node, value, label, AUTOFILL_FILL); +} + +void RenderView::didSelectAutoFillSuggestion(const WebKit::WebNode& node, + const WebKit::WebString& value, + const WebKit::WebString& label) { + didClearAutoFillSelection(node); + QueryAutoFillFormData(node, value, label, AUTOFILL_PREVIEW); +} +void RenderView::didClearAutoFillSelection(const WebKit::WebNode& node) { webkit_glue::FormData form; - const WebInputElement element = node.toConst<WebInputElement>(); + const WebFormControlElement element = node.toConst<WebFormControlElement>(); if (!form_manager_.FindFormWithFormControlElement( element, FormManager::REQUIRE_NONE, &form)) return; - - Send(new ViewHostMsg_FillAutoFillFormData( - routing_id_, autofill_query_id_, form, value, label)); + form_manager_.ClearPreviewedForm(form); } // WebKit::WebWidgetClient ---------------------------------------------------- @@ -5044,3 +5057,21 @@ bool RenderView::IsNonLocalTopLevelNavigation( } return false; } + +void RenderView::QueryAutoFillFormData(const WebKit::WebNode& node, + const WebKit::WebString& value, + const WebKit::WebString& label, + 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; + Send(new ViewHostMsg_FillAutoFillFormData( + routing_id_, autofill_query_id_, form, value, label)); +} diff --git a/chrome/renderer/render_view.h b/chrome/renderer/render_view.h index 5da8469..60eb2a7 100644 --- a/chrome/renderer/render_view.h +++ b/chrome/renderer/render_view.h @@ -282,6 +282,11 @@ class RenderView : public RenderWidget, const WebKit::WebNode& node, const WebKit::WebString& value, const WebKit::WebString& label); + virtual void didSelectAutoFillSuggestion( + const WebKit::WebNode& node, + const WebKit::WebString& value, + const WebKit::WebString& label); + virtual void didClearAutoFillSelection(const WebKit::WebNode& node); virtual WebKit::WebNotificationPresenter* GetNotificationPresenter() { return notification_provider_.get(); @@ -571,6 +576,12 @@ class RenderView : public RenderWidget, typedef std::map<GURL, ContentSettings> HostContentSettings; typedef std::map<GURL, int> HostZoomLevels; + enum AutoFillAction { + AUTOFILL_NONE, // No state set. + AUTOFILL_FILL, // Fill the AutoFill form data. + AUTOFILL_PREVIEW, // Preview the AutoFill form data. + }; + explicit RenderView(RenderThreadBase* render_thread, const WebPreferences& webkit_preferences, int64 session_storage_namespace_id); @@ -929,6 +940,15 @@ class RenderView : public RenderWidget, WebKit::WebFrame* frame, WebKit::WebNavigationType type); + // Queries the AutoFillManager for form data for the form containing |node|. + // |value| is the current text in the field, and |label| is the selected + // profile label. |action| specifies whether to Fill or Preview the values + // returned from the AutoFillManager. + void QueryAutoFillFormData(const WebKit::WebNode& node, + const WebKit::WebString& value, + const WebKit::WebString& label, + AutoFillAction action); + // Bitwise-ORed set of extra bindings that have been enabled. See // BindingsPolicy for details. int enabled_bindings_; @@ -1221,6 +1241,9 @@ class RenderView : public RenderWidget, PepperPluginDelegateImpl pepper_delegate_; + // The action to take when receiving AutoFill data from the AutoFillManager. + AutoFillAction autofill_action_; + DISALLOW_COPY_AND_ASSIGN(RenderView); }; |