// Copyright (c) 2011 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 #include "base/strings/utf_string_conversions.h" #include "base/time/time.h" #include "build/build_config.h" #include "chrome/test/base/chrome_render_view_test.h" #include "components/autofill/content/common/autofill_messages.h" #include "components/autofill/content/renderer/autofill_agent.h" #include "components/autofill/core/common/form_data.h" #include "content/public/test/mock_render_thread.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebElement.h" #include "third_party/WebKit/public/web/WebFormElement.h" #include "third_party/WebKit/public/web/WebInputElement.h" #include "third_party/WebKit/public/web/WebLocalFrame.h" using blink::WebDocument; using blink::WebElement; using blink::WebInputElement; using blink::WebString; typedef ChromeRenderViewTest FormAutocompleteTest; namespace autofill { namespace { // Helper function to verify the form-related messages received from the // renderer. The same data is expected in both messages. Depending on // |expect_submitted_message|, will verify presence of FormSubmitted message. void VerifyReceivedRendererMessages(content::MockRenderThread* render_thread, const std::string& fname, const std::string& lname, bool expect_submitted_message) { const IPC::Message* will_submit_message = render_thread->sink().GetFirstMessageMatching( AutofillHostMsg_WillSubmitForm::ID); const IPC::Message* submitted_message = render_thread->sink().GetFirstMessageMatching( AutofillHostMsg_FormSubmitted::ID); ASSERT_TRUE(will_submit_message != NULL); ASSERT_EQ(expect_submitted_message, submitted_message != NULL); // The tuple also includes a timestamp, which is ignored. std::tuple will_submit_forms; AutofillHostMsg_WillSubmitForm::Read(will_submit_message, &will_submit_forms); ASSERT_EQ(2U, std::get<0>(will_submit_forms).fields.size()); FormFieldData& will_submit_form_field = std::get<0>(will_submit_forms).fields[0]; EXPECT_EQ(WebString("fname"), will_submit_form_field.name); EXPECT_EQ(WebString(base::UTF8ToUTF16(fname)), will_submit_form_field.value); will_submit_form_field = std::get<0>(will_submit_forms).fields[1]; EXPECT_EQ(WebString("lname"), will_submit_form_field.name); EXPECT_EQ(WebString(base::UTF8ToUTF16(lname)), will_submit_form_field.value); if (expect_submitted_message) { std::tuple submitted_forms; AutofillHostMsg_FormSubmitted::Read(submitted_message, &submitted_forms); ASSERT_EQ(2U, std::get<0>(submitted_forms).fields.size()); FormFieldData& submitted_field = std::get<0>(submitted_forms).fields[0]; EXPECT_EQ(WebString("fname"), submitted_field.name); EXPECT_EQ(WebString(base::UTF8ToUTF16(fname)), submitted_field.value); submitted_field = std::get<0>(submitted_forms).fields[1]; EXPECT_EQ(WebString("lname"), submitted_field.name); EXPECT_EQ(WebString(base::UTF8ToUTF16(lname)), submitted_field.value); } } // Helper function to verify that NO form-related messages are received from the // renderer. void VerifyNoSubmitMessagesReceived(content::MockRenderThread* render_thread) { // No submission messages sent. const IPC::Message* will_submit_message = render_thread->sink().GetFirstMessageMatching( AutofillHostMsg_WillSubmitForm::ID); const IPC::Message* submitted_message = render_thread->sink().GetFirstMessageMatching( AutofillHostMsg_FormSubmitted::ID); EXPECT_EQ(NULL, will_submit_message); EXPECT_EQ(NULL, submitted_message); } // Simulates receiving a message from the browser to fill a form. void SimulateOnFillForm(content::MockRenderThread* render_thread, autofill::AutofillAgent* autofill_agent, blink::WebFrame* main_frame) { WebDocument document = main_frame->document(); WebElement element = document.getElementById(WebString::fromUTF8("fname")); ASSERT_FALSE(element.isNull()); // This call is necessary to setup the autofill agent appropriate for the // user selection; simulates the menu actually popping up. render_thread->sink().ClearMessages(); static_cast(autofill_agent) ->FormControlElementClicked(element.to(), false); FormData data; data.name = base::ASCIIToUTF16("name"); data.origin = GURL("http://example.com/"); data.action = GURL("http://example.com/blade.php"); data.is_form_tag = true; // Default value. FormFieldData field_data; field_data.name = base::ASCIIToUTF16("fname"); field_data.value = base::ASCIIToUTF16("John"); field_data.is_autofilled = true; data.fields.push_back(field_data); field_data.name = base::ASCIIToUTF16("lname"); field_data.value = base::ASCIIToUTF16("Smith"); field_data.is_autofilled = true; data.fields.push_back(field_data); AutofillMsg_FillForm msg(0, 0, data); static_cast(autofill_agent) ->OnMessageReceived(msg); } } // end namespace // Tests that submitting a form generates WillSubmitForm and FormSubmitted // messages with the form fields. TEST_F(FormAutocompleteTest, NormalFormSubmit) { // Load a form. LoadHTML("
" "
"); // Submit the form. ExecuteJavaScriptForTests("document.getElementById('myForm').submit();"); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "Rick", "Deckard", true /* expect_submitted_message */); } // Tests that submitting a form that prevents the submit event from propagating // will only send the WillSubmitForm message. TEST_F(FormAutocompleteTest, SubmitEventPrevented) { // Load a form. LoadHTML( "
" "
" ""); // Submit the form. ExecuteJavaScriptForTests( "var form = document.forms[0];" "form.onsubmit = function(event) { event.preventDefault(); };" "document.querySelector('input[type=submit]').click();"); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "Rick", "Deckard", false /* expect_submitted_message */); } // Tests that completing an Ajax request and having the form disappear will // trigger submission from Autofill's point of view. TEST_F(FormAutocompleteTest, AjaxSucceeded_NoLongerVisible) { // Load a form. LoadHTML( "
" "" "
"); // Simulate user input so that the form is "remembered". WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8("fname")); ASSERT_FALSE(element.isNull()); WebInputElement fname_element = element.to(); SimulateUserInputChangeForElement(&fname_element, std::string("Rick")); // Simulate removing the form just before the ajax request completes. ExecuteJavaScriptForTests( "var element = document.getElementById('myForm');" "element.parentNode.removeChild(element);"); // Simulate an Ajax request completing. static_cast(autofill_agent_)->ajaxSucceeded(); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "Rick", "Deckard", true /* expect_submitted_message */); } // Tests that completing an Ajax request and having the form with a specific // action disappear will trigger submission from Autofill's point of view, even // if there is another form with the same data but different action on the page. TEST_F(FormAutocompleteTest, AjaxSucceeded_NoLongerVisible_DifferentActionsSameData) { // Load a form. LoadHTML( "
" "" "
" "
" "" "
"); // Simulate user input so that the form is "remembered". WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8("fname")); ASSERT_FALSE(element.isNull()); WebInputElement fname_element = element.to(); SimulateUserInputChangeForElement(&fname_element, std::string("Rick")); // Simulate removing the form just before the ajax request completes. ExecuteJavaScriptForTests( "var element = document.getElementById('myForm');" "element.parentNode.removeChild(element);"); // Simulate an Ajax request completing. static_cast(autofill_agent_)->ajaxSucceeded(); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "Rick", "Deckard", true /* expect_submitted_message */); } // Tests that completing an Ajax request and having the form with no action // specified disappear will trigger submission from Autofill's point of view, // even if there is still another form with no action in the page. It will // compare field data within the forms. // TODO(kolos) Re-enable when the implementation of IsFormVisible is on-par // for these platforms. #if defined(OS_MACOSX) || defined(OS_ANDROID) #define MAYBE_NoLongerVisibleBothNoActions DISABLED_NoLongerVisibleBothNoActions #else #define MAYBE_NoLongerVisibleBothNoActions NoLongerVisibleBothNoActions #endif TEST_F(FormAutocompleteTest, MAYBE_NoLongerVisibleBothNoActions) { // Load a form. LoadHTML( "
" "" "
" "
" "" "
"); // Simulate user input so that the form is "remembered". WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8("fname")); ASSERT_FALSE(element.isNull()); WebInputElement fname_element = element.to(); SimulateUserInputChangeForElement(&fname_element, std::string("Rick")); // Simulate removing the form just before the ajax request completes. ExecuteJavaScriptForTests( "var element = document.getElementById('myForm');" "element.parentNode.removeChild(element);"); // Simulate an Ajax request completing. static_cast(autofill_agent_)->ajaxSucceeded(); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "Rick", "Deckard", true /* expect_submitted_message */); } // Tests that completing an Ajax request and having the form with no action // specified disappear will trigger submission from Autofill's point of view. TEST_F(FormAutocompleteTest, AjaxSucceeded_NoLongerVisible_NoAction) { // Load a form. LoadHTML( "
" "" "
"); // Simulate user input so that the form is "remembered". WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8("fname")); ASSERT_FALSE(element.isNull()); WebInputElement fname_element = element.to(); SimulateUserInputChangeForElement(&fname_element, std::string("Rick")); // Simulate removing the form just before the ajax request completes. ExecuteJavaScriptForTests("var element = document.getElementById('myForm');" "element.parentNode.removeChild(element);"); // Simulate an Ajax request completing. static_cast(autofill_agent_)->ajaxSucceeded(); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "Rick", "Deckard", true /* expect_submitted_message */); } // Tests that completing an Ajax request but leaving a form visible will not // trigger submission from Autofill's point of view. TEST_F(FormAutocompleteTest, AjaxSucceeded_StillVisible) { // Load a form. LoadHTML( "
" "" "
"); // Simulate user input so that the form is "remembered". WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8("fname")); ASSERT_FALSE(element.isNull()); WebInputElement fname_element = element.to(); SimulateUserInputChangeForElement(&fname_element, std::string("Rick")); // Simulate an Ajax request completing. static_cast(autofill_agent_)->ajaxSucceeded(); ProcessPendingMessages(); // No submission messages sent. VerifyNoSubmitMessagesReceived(render_thread_.get()); } // Tests that completing an Ajax request without any prior form interaction // does not trigger form submission from Autofill's point of view. TEST_F(FormAutocompleteTest, AjaxSucceeded_NoFormInteractionInvisible) { // Load a form. LoadHTML( "
" "" "
"); // No form interaction. // Simulate removing the form just before the ajax request completes. ExecuteJavaScriptForTests("var element = document.getElementById('myForm');" "element.parentNode.removeChild(element);"); // Simulate an Ajax request completing without prior user interaction. static_cast(autofill_agent_)->ajaxSucceeded(); ProcessPendingMessages(); // No submission messages sent. VerifyNoSubmitMessagesReceived(render_thread_.get()); } // Tests that completing an Ajax request after having autofilled a form, // with the form disappearing, will trigger submission from Autofill's // point of view. TEST_F(FormAutocompleteTest, AjaxSucceeded_FilledFormIsInvisible) { // Load a form. LoadHTML( "
" "" "
"); // Simulate filling a form using Autofill. SimulateOnFillForm(render_thread_.get(), autofill_agent_, GetMainFrame()); // Simulate removing the form just before the ajax request completes. ExecuteJavaScriptForTests("var element = document.getElementById('myForm');" "element.parentNode.removeChild(element);"); // Simulate an Ajax request completing. static_cast(autofill_agent_)->ajaxSucceeded(); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "John", "Smith", true /* expect_submitted_message */); } // Tests that completing an Ajax request after having autofilled a form, // without the form disappearing, will not trigger submission from Autofill's // point of view. TEST_F(FormAutocompleteTest, AjaxSucceeded_FilledFormStillVisible) { // Load a form. LoadHTML( "
" "" "
"); // Simulate filling a form using Autofill. SimulateOnFillForm(render_thread_.get(), autofill_agent_, GetMainFrame()); // Form still visible. // Simulate an Ajax request completing. static_cast(autofill_agent_)->ajaxSucceeded(); ProcessPendingMessages(); // No submission messages sent. VerifyNoSubmitMessagesReceived(render_thread_.get()); } // Test that a FocusNoLongerOnForm message is sent if focus goes from an // interacted form to an element outside the form. TEST_F(FormAutocompleteTest, InteractedFormNoLongerFocused_FocusNoLongerOnForm) { // Load a form. LoadHTML( "" "
" "" "
"); // Simulate user input so that the form is "remembered". WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8("fname")); ASSERT_FALSE(element.isNull()); WebInputElement fname_element = element.to(); SimulateUserInputChangeForElement(&fname_element, std::string("Rick")); // Change focus to a different node outside the form. WebElement different = document.getElementById(WebString::fromUTF8("different")); SetFocused(different); ProcessPendingMessages(); EXPECT_TRUE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_FocusNoLongerOnForm::ID) != nullptr); } // Test that a FocusNoLongerOnForm message is sent if focus goes from one // interacted form to another. TEST_F(FormAutocompleteTest, InteractingInDifferentForms_FocusNoLongerOnForm) { // Load a form. LoadHTML( "
" "" "
" "
" "" "
"); // Simulate user input in the first form so that the form is "remembered". WebDocument document = GetMainFrame()->document(); WebElement element = document.getElementById(WebString::fromUTF8("fname")); ASSERT_FALSE(element.isNull()); WebInputElement fname_element = element.to(); SimulateUserInputChangeForElement(&fname_element, std::string("Rick")); // Simulate user input in the second form so that a "no longer focused" // message is sent for the first form. document = GetMainFrame()->document(); element = document.getElementById(WebString::fromUTF8("fname2")); ASSERT_FALSE(element.isNull()); fname_element = element.to(); SimulateUserInputChangeForElement(&fname_element, std::string("John")); ProcessPendingMessages(); EXPECT_TRUE(render_thread_->sink().GetFirstMessageMatching( AutofillHostMsg_FocusNoLongerOnForm::ID) != nullptr); } // Tests that submitting a form that has autocomplete="off" generates // WillSubmitForm and FormSubmitted messages. TEST_F(FormAutocompleteTest, AutoCompleteOffFormSubmit) { // Load a form. LoadHTML("
" "" "" "
"); // Submit the form. ExecuteJavaScriptForTests("document.getElementById('myForm').submit();"); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "Rick", "Deckard", true /* expect_submitted_message */); } // Tests that fields with autocomplete off are submitted. TEST_F(FormAutocompleteTest, AutoCompleteOffInputSubmit) { // Load a form. LoadHTML("
" "" "" "
"); // Submit the form. ExecuteJavaScriptForTests("document.getElementById('myForm').submit();"); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "Rick", "Deckard", true /* expect_submitted_message */); } // Tests that submitting a form that has been dynamically set as autocomplete // off generates WillSubmitForm and FormSubmitted messages. // Note: We previously did the opposite, for bug http://crbug.com/36520 TEST_F(FormAutocompleteTest, DynamicAutoCompleteOffFormSubmit) { LoadHTML("
" "
"); WebElement element = GetMainFrame()->document().getElementById(blink::WebString("myForm")); ASSERT_FALSE(element.isNull()); blink::WebFormElement form = element.to(); EXPECT_TRUE(form.autoComplete()); // Dynamically mark the form as autocomplete off. ExecuteJavaScriptForTests( "document.getElementById('myForm')." "setAttribute('autocomplete', 'off');"); ProcessPendingMessages(); EXPECT_FALSE(form.autoComplete()); // Submit the form. ExecuteJavaScriptForTests("document.getElementById('myForm').submit();"); ProcessPendingMessages(); VerifyReceivedRendererMessages(render_thread_.get(), "Rick", "Deckard", true /* expect_submitted_message */); } } // namespace autofill