Index: chrome/browser/password_manager/password_manager_browsertest.cc |
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc |
index f759c2bb152c07a38ecf65c780b279a65185689a..383c25e36ad39d049b5f471c00b482783d035180 100644 |
--- a/chrome/browser/password_manager/password_manager_browsertest.cc |
+++ b/chrome/browser/password_manager/password_manager_browsertest.cc |
@@ -48,6 +48,7 @@ |
#include "content/public/test/browser_test_utils.h" |
#include "content/public/test/test_utils.h" |
#include "net/base/filename_util.h" |
+#include "net/dns/mock_host_resolver.h" |
#include "net/test/embedded_test_server/embedded_test_server.h" |
#include "net/test/embedded_test_server/http_request.h" |
#include "net/test/embedded_test_server/http_response.h" |
@@ -289,6 +290,29 @@ scoped_ptr<net::test_server::HttpResponse> HandleTestAuthRequest( |
} |
} |
+// Handle |request| to arbitrary domain and respond with |response_content_| |
+std::string response_content_; |
+scoped_ptr<net::test_server::HttpResponse> HandleTestRequest( |
Garrett Casto
2015/05/26 20:31:37
So after thinking about it some more, I don't thin
|
+ const net::test_server::HttpRequest& request) { |
+ scoped_ptr<net::test_server::BasicHttpResponse> http_response( |
+ new net::test_server::BasicHttpResponse()); |
+ http_response->set_code(net::HTTP_OK); |
+ http_response->set_content_type("text/html"); |
+ if (request.relative_url == "/done.html") { |
Garrett Casto
2015/05/26 20:31:37
Looks like this could be solved by having the path
|
+ std::string alt_response = |
+ "<html>" |
+ "<body>" |
+ "<a id='link' href='done.html'>Go somewhere</a>" |
+ "</body>" |
+ "</html>"; |
+ http_response->set_content(alt_response); |
+ } else { |
+ http_response->set_content(response_content_); |
+ } |
+ return http_response.Pass(); |
+} |
+ |
+ |
} // namespace |
@@ -345,10 +369,18 @@ class PasswordManagerBrowserTest : public InProcessBrowserTest { |
// event loop is spun to allow all other possible events to take place. |
void WaitForElementValue(const std::string& element_id, |
const std::string& expected_value); |
+ // Same as above except the element |element_id| is in iframe |iframe_id| |
+ void WaitForElementValue(const std::string& iframe_id, |
+ const std::string& element_id, |
+ const std::string& expected_value); |
// Checks that the current "value" attribute of the HTML element with |
// |element_id| is equal to |expected_value|. |
void CheckElementValue(const std::string& element_id, |
const std::string& expected_value); |
+ // Same as above except the element |element_id| is in iframe |iframe_id| |
+ void CheckElementValue(const std::string& iframe_id, |
+ const std::string& element_id, |
+ const std::string& expected_value); |
// TODO(dvadym): Remove this once history.pushState() handling is not behind |
// a flag. |
@@ -366,6 +398,15 @@ class PasswordManagerBrowserTest : public InProcessBrowserTest { |
void PasswordManagerBrowserTest::WaitForElementValue( |
const std::string& element_id, |
const std::string& expected_value) { |
+ PasswordManagerBrowserTest::WaitForElementValue("null", |
+ element_id, |
+ expected_value); |
+} |
+ |
+void PasswordManagerBrowserTest::WaitForElementValue( |
+ const std::string& iframe_id, |
+ const std::string& element_id, |
+ const std::string& expected_value) { |
enum ReturnCodes { // Possible results of the JavaScript code. |
RETURN_CODE_OK, |
RETURN_CODE_NO_ELEMENT, |
@@ -374,9 +415,17 @@ void PasswordManagerBrowserTest::WaitForElementValue( |
}; |
const std::string value_check_function = base::StringPrintf( |
"function valueCheck() {" |
- " var element = document.getElementById('%s');" |
+ " var element;" |
Garrett Casto
2015/05/26 20:31:37
This is a counter intuitive, but this line isn't n
|
+ " if (%s)" |
+ " var element = document.getElementById(" |
+ " '%s').contentDocument.getElementById('%s');" |
+ " else " |
+ " var element = document.getElementById('%s');" |
" return element && element.value == '%s';" |
"}", |
+ iframe_id.c_str(), |
+ iframe_id.c_str(), |
+ element_id.c_str(), |
element_id.c_str(), |
expected_value.c_str()); |
const std::string script = |
@@ -386,7 +435,12 @@ void PasswordManagerBrowserTest::WaitForElementValue( |
" /* Spin the event loop with setTimeout. */" |
" setTimeout(window.domAutomationController.send(%d), 0);" |
"} else {" |
- " var element = document.getElementById('%s');" |
+ " var element;" |
Garrett Casto
2015/05/26 20:31:37
Same here.
|
+ " if (%s)" |
+ " var element = document.getElementById(" |
+ " '%s').contentDocument.getElementById('%s');" |
+ " else " |
+ " var element = document.getElementById('%s');" |
" if (!element)" |
" window.domAutomationController.send(%d);" |
" element.onchange = function() {" |
@@ -399,6 +453,9 @@ void PasswordManagerBrowserTest::WaitForElementValue( |
" };" |
"}", |
RETURN_CODE_OK, |
+ iframe_id.c_str(), |
+ iframe_id.c_str(), |
+ element_id.c_str(), |
element_id.c_str(), |
RETURN_CODE_NO_ELEMENT, |
RETURN_CODE_OK, |
@@ -414,9 +471,26 @@ void PasswordManagerBrowserTest::WaitForElementValue( |
void PasswordManagerBrowserTest::CheckElementValue( |
const std::string& element_id, |
const std::string& expected_value) { |
+ PasswordManagerBrowserTest::CheckElementValue("null", |
+ element_id, |
+ expected_value); |
+} |
+ |
+void PasswordManagerBrowserTest::CheckElementValue( |
+ const std::string& iframe_id, |
+ const std::string& element_id, |
+ const std::string& expected_value) { |
const std::string value_check_script = base::StringPrintf( |
- "var element = document.getElementById('%s');" |
+ "var element;" |
Garrett Casto
2015/05/26 20:31:37
Same here.
|
+ "if (%s)" |
+ " var element = document.getElementById(" |
+ " '%s').contentDocument.getElementById('%s');" |
+ "else " |
+ " var element = document.getElementById('%s');" |
"window.domAutomationController.send(element && element.value == '%s');", |
+ iframe_id.c_str(), |
+ iframe_id.c_str(), |
+ element_id.c_str(), |
element_id.c_str(), |
expected_value.c_str()); |
bool return_value = false; |
@@ -1913,3 +1987,183 @@ IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, BaseTagWithNoActionTest) { |
// Wait until that interaction causes the password value to be revealed. |
WaitForElementValue("password_field", "mypassword"); |
} |
+ |
+// Check that a password form in an iframe of different origin will not be |
+// filled in until a user interact with the form. |
+IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, CrossSiteIframeNotFillTest) { |
+ // Setup the mock host resolver |
+ host_resolver()->AddRule("*", "127.0.0.1"); |
+ response_content_ = "<html>" |
+ "<head>" |
+ " <meta charset='utf-8' />" |
+ " <title>Title</title>" |
+ "</head>" |
+ "<body id='inframe'>" |
+ "<script>" |
+ "function receiveMessage(event){" |
+ " if (event.data == 'init_fill') { " |
Garrett Casto
2015/05/26 20:31:37
Maybe this message should be "fill_and_submit" sin
|
+ " document.getElementById(" |
+ " 'username').value = 'temp';" |
+ " document.getElementById(" |
+ " 'password').value = 'pa55w0rd';" |
+ " document.getElementById('submit_button').click();" |
+ " } else if (event.data == 'get_username') {" |
+ " event.source.postMessage(" |
+ " document.getElementById('username').value," |
+ " event.origin);" |
+ " } else if (event.data == 'get_password') {" |
+ " event.source.postMessage(" |
+ " document.getElementById('password').value," |
+ " event.origin);" |
+ " } else if (event.data == 'check_for_username') {" |
Garrett Casto
2015/05/26 20:31:37
These two "check" messages don't look like they ar
|
+ " waitForValue('username', 'temp', event)" |
+ " } else if (event.data == 'check_for_password') {" |
+ " waitForValue('password', 'pa55w0rd', event)" |
+ " }" |
+ "}" |
+ "window.addEventListener('message'," |
+ " receiveMessage, false);" |
+ "" |
+ "function valueCheck(id, expect_value) {" |
Garrett Casto
2015/05/26 20:31:37
Looks like these two functions aren't necessary.
|
+ " var element = document.getElementById(id);" |
+ " return element && element.value == expect_value;" |
+ "}" |
+ "" |
+ "function waitForValue(id, expect_value, event){" |
+ " var element = document.getElementById(id);" |
+ " if (valueCheck(id, expect_value)) {" |
+ " /* Spin the event loop with setTimeout. */" |
+ " setTimeout(event.source.postMessage(" |
+ " element.value, event.origin), 0);" |
+ " } else {" |
+ " element.onchange = function() {" |
+ " if (valueCheck(id, expect_value)) {" |
+ " /* Spin the event loop with setTimeout. */" |
+ " setTimeout(event.source.postMessage(" |
+ " element.value, event.origin), 0);" |
+ " }" |
+ " };" |
+ " }" |
+ "}" |
+ "" |
+ "</script>" |
+ "<form method='POST' name='TestForm' action='done.html'>" |
+ " <input type='text' id='username'/>" |
+ " <input type='password' id='password'/>" |
+ " <input type='submit' id='submit_button' value='Log'/>" |
+ "</form>" |
+ "</body>" |
+ "</html>"; |
+ // Set up customized request handler |
+ embedded_test_server()->RegisterRequestHandler( |
+ base::Bind(&HandleTestRequest)); |
+ |
+ // Visit the sign-up form to store a password for autofill later |
Garrett Casto
2015/05/26 20:31:38
Nit: The code right after this comment actually se
|
+ NavigateToFile("/password/password_form_in_crosssite_iframe.html"); |
+ NavigationObserver ifrm_observer(WebContents()); |
+ ifrm_observer.SetPathToWaitFor("/randomFile.html"); |
+ |
+ std::string submit = base::StringPrintf( |
Garrett Casto
2015/05/26 20:31:37
Nit: I'd name this string something more like "cre
|
+ "createCrossSiteIframe('http://randomsite.net:%d/randomFile.html');", |
+ embedded_test_server()->port()); |
+ |
+ ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), submit)); |
+ ifrm_observer.Wait(); |
+ ifrm_observer.SetPathToWaitFor(""); |
Garrett Casto
2015/05/26 20:31:37
This line looks superfluous as you change the stri
|
+ |
+ NavigationObserver init_observer(WebContents()); |
+ init_observer.SetPathToWaitFor("/done.html"); |
+ scoped_ptr<PromptObserver> prompt_observer( |
+ PromptObserver::Create(WebContents())); |
+ std::string init_form = "sendMessage('init_fill');"; |
+ ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), init_form)); |
+ init_observer.Wait(); |
+ EXPECT_TRUE(prompt_observer->IsShowingPrompt()); |
+ prompt_observer->Accept(); |
+ |
+ // Visit the form again |
+ NavigationObserver reload_observer(WebContents()); |
+ NavigateToFile("/password/password_form_in_crosssite_iframe.html"); |
+ reload_observer.Wait(); |
+ |
+ NavigationObserver ifrm_observer_2(WebContents()); |
+ ifrm_observer_2.SetPathToWaitFor("/randomFile.html"); |
+ ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), submit)); |
+ ifrm_observer_2.Wait(); |
+ |
+ // Verfiy username is not autofilled |
+ std::string empty_username; |
+ ASSERT_TRUE(content::ExecuteScriptAndExtractString( |
+ RenderViewHost(), |
+ "sendMessage('get_username');", |
+ &empty_username)); |
+ ASSERT_EQ("", empty_username); |
+ |
+ |
+ // Verfiy password is not autofilled |
+ std::string empty_password; |
+ ASSERT_TRUE(content::ExecuteScriptAndExtractString( |
+ RenderViewHost(), |
+ "sendMessage('get_password');", |
+ &empty_password)); |
+ ASSERT_EQ("", empty_password); |
+} |
+ |
+// Check that a password form in an iframe of different origin will not be |
+// filled in until a user interact with the form. |
Garrett Casto
2015/05/26 20:31:37
This comment looks like it's from the other test.
|
+IN_PROC_BROWSER_TEST_F(PasswordManagerBrowserTest, |
+ SameOriginIframeAutoFillTest) { |
+ // Visit the sign-up form to store a password for autofill later |
+ NavigateToFile("/password/password_form_in_same_origin_iframe.html"); |
+ NavigationObserver observer(WebContents()); |
+ observer.SetPathToWaitFor("/password/done.html"); |
+ scoped_ptr<PromptObserver> prompt_observer( |
+ PromptObserver::Create(WebContents())); |
+ |
+ std::string submit = |
+ "var ifrmDoc = document.getElementById('iframe').contentDocument;" |
+ "ifrmDoc.getElementById('username_field').value = 'temp';" |
+ "ifrmDoc.getElementById('password_field').value = 'pa55w0rd';" |
+ "ifrmDoc.getElementById('input_submit_button').click();"; |
+ ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), submit)); |
+ observer.Wait(); |
+ EXPECT_TRUE(prompt_observer->IsShowingPrompt()); |
+ prompt_observer->Accept(); |
+ |
+ // Visit the form again |
+ NavigationObserver reload_observer(WebContents()); |
+ NavigateToFile("/password/password_form_in_same_origin_iframe.html"); |
+ reload_observer.Wait(); |
+ |
+ // Verfiy username is autofilled |
+ CheckElementValue("iframe", "username_field", "temp"); |
+ |
+ // Verfiy password is not autofilled |
+ CheckElementValue("iframe", "password_field", ""); |
+ |
+ // Simulate the user interaction in the iframe which should trigger autofill. |
+ ASSERT_TRUE(content::ExecuteScript( |
+ RenderViewHost(), |
+ "var iframeRect = document.getElementById(" |
+ "'iframe').getBoundingClientRect();")); |
+ int top; |
+ ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
+ RenderViewHost(), |
+ "window.domAutomationController.send(iframeRect.top);", |
+ &top)); |
+ int left; |
+ ASSERT_TRUE(content::ExecuteScriptAndExtractInt( |
+ RenderViewHost(), |
+ "window.domAutomationController.send(iframeRect.left);", |
+ &left)); |
+ |
+ content::SimulateMouseClickAt( |
+ WebContents(), 0, blink::WebMouseEvent::ButtonLeft, gfx::Point(left + 1, |
+ top + 1)); |
+ // Verify password has been autofilled |
+ WaitForElementValue("iframe", "password_field", "pa55w0rd"); |
+ |
+ // Verfiy username has been autofilled |
+ CheckElementValue("iframe", "username_field", "temp"); |
+ |
+} |