summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorxunlu <xunlu@chromium.org>2015-06-01 18:17:54 -0700
committerCommit bot <commit-bot@chromium.org>2015-06-02 01:18:22 +0000
commit4bb54b508f665bfaaa3b0c64ee4cc1d28f138bec (patch)
tree160865c3b373a06b0ea06eaf0d3337af666f9313
parentfd5c957e3f9d2f5c6446e5b5d77e61502321a24e (diff)
downloadchromium_src-4bb54b508f665bfaaa3b0c64ee4cc1d28f138bec.zip
chromium_src-4bb54b508f665bfaaa3b0c64ee4cc1d28f138bec.tar.gz
chromium_src-4bb54b508f665bfaaa3b0c64ee4cc1d28f138bec.tar.bz2
Allow autofill in iframe inside page of same origin
Previously password manager will not autofill in iframe regardless of its origin. This change will allow autofill in iframes that have the same origin as the enclosing page. BUG=345371 Review URL: https://codereview.chromium.org/1159513002 Cr-Commit-Position: refs/heads/master@{#332299}
-rw-r--r--chrome/browser/password_manager/password_manager_browsertest.cc204
-rw-r--r--chrome/renderer/autofill/password_autofill_agent_browsertest.cc66
-rw-r--r--chrome/test/data/password/crossite_iframe_content.html31
-rw-r--r--chrome/test/data/password/password_form_in_crosssite_iframe.html25
-rw-r--r--chrome/test/data/password/password_form_in_same_origin_iframe.html8
-rw-r--r--components/autofill/content/renderer/password_autofill_agent.cc17
6 files changed, 278 insertions, 73 deletions
diff --git a/chrome/browser/password_manager/password_manager_browsertest.cc b/chrome/browser/password_manager/password_manager_browsertest.cc
index 83de931..8891f04 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"
@@ -345,10 +346,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 +375,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 +392,16 @@ void PasswordManagerBrowserTest::WaitForElementValue(
};
const std::string value_check_function = base::StringPrintf(
"function valueCheck() {"
- " var element = document.getElementById('%s');"
+ " 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 +411,11 @@ void PasswordManagerBrowserTest::WaitForElementValue(
" /* Spin the event loop with setTimeout. */"
" setTimeout(window.domAutomationController.send(%d), 0);"
"} else {"
- " var element = document.getElementById('%s');"
+ " 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 +428,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 +446,25 @@ 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');"
+ "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;
@@ -1921,3 +1969,153 @@ 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");
+
+ // Here we need to dynamically create the iframe because the port
+ // embedded_test_server ran on was dynamically allocated, so the iframe's src
+ // attribute can only be determined at run time.
+ NavigateToFile("/password/password_form_in_crosssite_iframe.html");
+ NavigationObserver ifrm_observer(WebContents());
+ ifrm_observer.SetPathToWaitFor("/password/crossite_iframe_content.html");
+ std::string create_iframe = base::StringPrintf(
+ "create_iframe("
+ "'http://randomsite.net:%d/password/crossite_iframe_content.html');",
+ embedded_test_server()->port());
+ ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), create_iframe));
+ ifrm_observer.Wait();
+
+ // Store a password for autofill later
+ NavigationObserver init_observer(WebContents());
+ init_observer.SetPathToWaitFor("/password/done.html");
+ scoped_ptr<PromptObserver> prompt_observer(
+ PromptObserver::Create(WebContents()));
+ std::string init_form = "sendMessage('fill_and_submit');";
+ 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("/password/crossite_iframe_content.html");
+ ASSERT_TRUE(content::ExecuteScript(RenderViewHost(), create_iframe));
+ ifrm_observer_2.Wait();
+
+ // Verify username is not autofilled
+ std::string empty_username;
+ ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+ RenderViewHost(),
+ "sendMessage('get_username');",
+ &empty_username));
+ ASSERT_EQ("", empty_username);
+ // Verify password is not autofilled
+ std::string empty_password;
+ ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+ RenderViewHost(),
+ "sendMessage('get_password');",
+ &empty_password));
+ ASSERT_EQ("", empty_password);
+
+ // Simulate the user interaction in the iframe and verify autofill is not
+ // triggered. Note this check is only best-effort because we don't know how
+ // long to wait before we are certain that no autofill will be triggered.
+ // Theoretically unexpected autofill can happen after this check.
+ 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 username is not autofilled
+ ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+ RenderViewHost(),
+ "sendMessage('get_username');",
+ &empty_username));
+ ASSERT_EQ("", empty_username);
+ // Verify password is not autofilled
+ ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+ RenderViewHost(),
+ "sendMessage('get_password');",
+ &empty_password));
+ ASSERT_EQ("", empty_password);
+}
+
+// Check that a password form in an iframe of same origin will not be
+// filled in until user interact with the iframe.
+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();
+
+ // Verify username is autofilled
+ CheckElementValue("iframe", "username_field", "temp");
+
+ // Verify 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");
+
+ // Verify username has been autofilled
+ CheckElementValue("iframe", "username_field", "temp");
+
+}
diff --git a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
index e7abb23..68a0cf3 100644
--- a/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
+++ b/chrome/renderer/autofill/password_autofill_agent_browsertest.cc
@@ -819,72 +819,6 @@ TEST_F(PasswordAutofillAgentTest, SendPasswordFormsTest_Redirection) {
AutofillHostMsg_PasswordFormsRendered::ID));
}
-// Tests that a password form in an iframe will not be filled in until a user
-// interaction with the form.
-TEST_F(PasswordAutofillAgentTest, IframeNoFillTest) {
- const char kIframeName[] = "iframe";
- const char kWebpageWithIframeStart[] =
- "<html>"
- " <head>"
- " <meta charset='utf-8' />"
- " <title>Title</title>"
- " </head>"
- " <body>"
- " <iframe name='iframe' src=\"";
- const char kWebpageWithIframeEnd[] =
- "\"></iframe>"
- " </body>"
- "</html>";
-
- std::string origin("data:text/html;charset=utf-8,");
- origin += kSimpleWebpage;
-
- std::string page_html(kWebpageWithIframeStart);
- page_html += origin;
- page_html += kWebpageWithIframeEnd;
-
- LoadHTML(page_html.c_str());
-
- // Set the expected form origin and action URLs.
- fill_data_.origin = GURL(origin);
- fill_data_.action = GURL(origin);
-
- // Retrieve the input elements from the iframe since that is where we want to
- // test the autofill.
- WebFrame* iframe = GetMainFrame()->findChildByName(kIframeName);
- ASSERT_TRUE(iframe);
-
- SimulateOnFillPasswordFormForFrame(iframe, fill_data_);
-
- WebDocument document = iframe->document();
-
- WebElement username_element = document.getElementById(kUsernameName);
- WebElement password_element = document.getElementById(kPasswordName);
- ASSERT_FALSE(username_element.isNull());
- ASSERT_FALSE(password_element.isNull());
-
- WebInputElement username_input = username_element.to<WebInputElement>();
- WebInputElement password_input = password_element.to<WebInputElement>();
- ASSERT_FALSE(username_element.isNull());
-
- CheckTextFieldsStateForElements(
- username_input, "", false, password_input, "", false, false);
-
- // Simulate the user typing in the username in the iframe which should cause
- // an autofill.
- content::RenderFrame::FromWebFrame(iframe)
- ->OnMessageReceived(AutofillMsg_FirstUserGestureObservedInTab(0));
- SimulateUserInputChangeForElement(&username_input, kAliceUsername);
-
- CheckTextFieldsStateForElements(username_input,
- kAliceUsername,
- true,
- password_input,
- kAlicePassword,
- true,
- false);
-}
-
// Tests that a password will only be filled as a suggested and will not be
// accessible by the DOM until a user gesture has occurred.
TEST_F(PasswordAutofillAgentTest, GestureRequiredTest) {
diff --git a/chrome/test/data/password/crossite_iframe_content.html b/chrome/test/data/password/crossite_iframe_content.html
new file mode 100644
index 0000000..bb7ee8f
--- /dev/null
+++ b/chrome/test/data/password/crossite_iframe_content.html
@@ -0,0 +1,31 @@
+<html>
+<head>
+ <meta charset="utf-8" />
+ <title>Title</title>
+</head>
+<body id="inframe">
+<script>
+function receiveMessage(event){
+ if (event.data == "fill_and_submit") {
+ 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);
+ }
+}
+window.addEventListener("message", receiveMessage, false);
+
+</script>
+<form method="POST" name="TestForm" action="/password/done.html">
+ <input type="text" id="username"/>
+ <input type="password" id="password"/>
+ <input type="submit" id="submit_button" value="Log"/>
+</form>
+</body>
+</html>
+
diff --git a/chrome/test/data/password/password_form_in_crosssite_iframe.html b/chrome/test/data/password/password_form_in_crosssite_iframe.html
new file mode 100644
index 0000000..01f095e
--- /dev/null
+++ b/chrome/test/data/password/password_form_in_crosssite_iframe.html
@@ -0,0 +1,25 @@
+<html>
+<body id="inParent">
+
+<script>
+ function receiveMessage(event) {
+ window.domAutomationController.send(event.data);
+ }
+
+ function sendMessage(msg) {
+ document.getElementById("iframe").contentWindow.postMessage(msg,"*");
+ }
+
+ function create_iframe(src) {
+ var ifrm = document.createElement("IFRAME");
+ ifrm.setAttribute("id", "iframe");
+ ifrm.setAttribute("name", "iframe");
+ ifrm.setAttribute("src", src);
+ document.body.appendChild(ifrm);
+ }
+
+ window.addEventListener("message", receiveMessage, false);
+</script>
+
+</body>
+</html>
diff --git a/chrome/test/data/password/password_form_in_same_origin_iframe.html b/chrome/test/data/password/password_form_in_same_origin_iframe.html
new file mode 100644
index 0000000..e12f045
--- /dev/null
+++ b/chrome/test/data/password/password_form_in_same_origin_iframe.html
@@ -0,0 +1,8 @@
+<html>
+<body>
+
+<iframe src="password_form.html" id="iframe" name="iframe">
+</iframe>
+
+</body>
+</html>
diff --git a/components/autofill/content/renderer/password_autofill_agent.cc b/components/autofill/content/renderer/password_autofill_agent.cc
index aa9fafa..0177625 100644
--- a/components/autofill/content/renderer/password_autofill_agent.cc
+++ b/components/autofill/content/renderer/password_autofill_agent.cc
@@ -450,10 +450,19 @@ bool FillFormOnPasswordReceived(
std::map<const blink::WebInputElement, blink::WebString>&
nonscript_modified_values,
base::Callback<void(blink::WebInputElement*)> registration_callback) {
- // Do not fill if the password field is in an iframe.
- DCHECK(password_element.document().frame());
- if (password_element.document().frame()->parent())
- return false;
+ // Do not fill if the password field is in a chain of iframes not having
+ // identical origin.
+ blink::WebFrame* cur_frame = password_element.document().frame();
+ blink::WebString bottom_frame_origin =
+ cur_frame->securityOrigin().toString();
+
+ DCHECK(cur_frame);
+
+ while (cur_frame->parent()) {
+ cur_frame = cur_frame->parent();
+ if (!bottom_frame_origin.equals(cur_frame->securityOrigin().toString()))
+ return false;
+ }
// If we can't modify the password, don't try to set the username
if (!IsElementAutocompletable(password_element))