Commit 48bc72d1 authored by Moe Ahmadi's avatar Moe Ahmadi Committed by Commit Bot

[AF] Fill multiple dynamic forms on one page.

- This CL expands on the dynamic form filling logic by tracking the filling
context for multiple forms, therefore allowing multiple dynamic forms on
the same page to get filled, e.g., billing and shipping address forms on
victoriassecret.com
_ It also fixes a bug where previously filled fields were not being
refilled even if their value was reset during the dynamic form change.
- It also rewrites the JS logic in the test html files to eliminate race
conditions and improve readability.

Change-Id: I44681742820d5285e77a4e9e4a61917ae4bfc41e
Reviewed-on: https://chromium-review.googlesource.com/1003381Reviewed-by: default avatarSebastien Seguin-Gagnon <sebsg@chromium.org>
Commit-Queue: Moe Ahmadi <mahmadi@chromium.org>
Cr-Commit-Position: refs/heads/master@{#550183}
parent 372fccea
...@@ -549,8 +549,8 @@ class AutofillInteractiveTest : public InProcessBrowserTest { ...@@ -549,8 +549,8 @@ class AutofillInteractiveTest : public InProcessBrowserTest {
ExpectFilledTestForm(); ExpectFilledTestForm();
} }
void TriggerFormFill() { void TriggerFormFill(const std::string& field_name) {
FocusFirstNameField(); FocusFieldByName(field_name);
// Start filling the first name field with "M" and wait for the popup to be // Start filling the first name field with "M" and wait for the popup to be
// shown. // shown.
...@@ -1829,7 +1829,7 @@ IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, DynamicChangingFormFill) { ...@@ -1829,7 +1829,7 @@ IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, DynamicChangingFormFill) {
embedded_test_server()->GetURL("/autofill/dynamic_form_disabled.html"); embedded_test_server()->GetURL("/autofill/dynamic_form_disabled.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url)); ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill(); TriggerFormFill("firstname");
// Wait for the re-fill to happen. // Wait for the re-fill to happen.
bool has_refilled = false; bool has_refilled = false;
...@@ -1838,13 +1838,13 @@ IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, DynamicChangingFormFill) { ...@@ -1838,13 +1838,13 @@ IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, DynamicChangingFormFill) {
ASSERT_FALSE(has_refilled); ASSERT_FALSE(has_refilled);
// Make sure that the new form was not filled. // Make sure that the new form was not filled.
ExpectFieldValue("firstname", ""); ExpectFieldValue("firstname_form1", "");
ExpectFieldValue("address1", ""); ExpectFieldValue("address_form1", "");
ExpectFieldValue("state", "CA"); // Default value. ExpectFieldValue("state_form1", "CA"); // Default value.
ExpectFieldValue("city", ""); ExpectFieldValue("city_form1", "");
ExpectFieldValue("company", ""); ExpectFieldValue("company_form1", "");
ExpectFieldValue("email", ""); ExpectFieldValue("email_form1", "");
ExpectFieldValue("phone", ""); ExpectFieldValue("phone_form1", "");
} }
// An extension of the test fixture for tests with site isolation. // An extension of the test fixture for tests with site isolation.
...@@ -2052,7 +2052,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest, ...@@ -2052,7 +2052,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
embedded_test_server()->GetURL("a.com", "/autofill/dynamic_form.html"); embedded_test_server()->GetURL("a.com", "/autofill/dynamic_form.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url)); ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill(); TriggerFormFill("firstname");
// Wait for the re-fill to happen. // Wait for the re-fill to happen.
bool has_refilled = false; bool has_refilled = false;
...@@ -2061,13 +2061,59 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest, ...@@ -2061,13 +2061,59 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
ASSERT_TRUE(has_refilled); ASSERT_TRUE(has_refilled);
// Make sure the new form was filled correctly. // Make sure the new form was filled correctly.
ExpectFieldValue("firstname", "Milton"); ExpectFieldValue("firstname_form1", "Milton");
ExpectFieldValue("address1", "4120 Freidrich Lane"); ExpectFieldValue("address_form1", "4120 Freidrich Lane");
ExpectFieldValue("state", "TX"); ExpectFieldValue("state_form1", "TX");
ExpectFieldValue("city", "Austin"); ExpectFieldValue("city_form1", "Austin");
ExpectFieldValue("company", "Initech"); ExpectFieldValue("company_form1", "Initech");
ExpectFieldValue("email", "red.swingline@initech.com"); ExpectFieldValue("email_form1", "red.swingline@initech.com");
ExpectFieldValue("phone", "15125551234"); ExpectFieldValue("phone_form1", "15125551234");
}
IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
TwoDynamicChangingFormsFill) {
// Setup that the test expects a re-fill to happen.
test_delegate()->SetIsExpectingDynamicRefill(true);
CreateTestProfile();
GURL url = embedded_test_server()->GetURL("a.com",
"/autofill/two_dynamic_forms.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill("firstname_form1");
// Wait for the re-fill to happen.
bool has_refilled = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetRenderViewHost(), "hasRefilled('firstname_form1')", &has_refilled));
ASSERT_TRUE(has_refilled);
// Make sure the new form was filled correctly.
ExpectFieldValue("firstname_form1", "Milton");
ExpectFieldValue("address_form1", "4120 Freidrich Lane");
ExpectFieldValue("state_form1", "TX");
ExpectFieldValue("city_form1", "Austin");
ExpectFieldValue("company_form1", "Initech");
ExpectFieldValue("email_form1", "red.swingline@initech.com");
ExpectFieldValue("phone_form1", "15125551234");
TriggerFormFill("firstname_form2");
// Wait for the re-fill to happen.
has_refilled = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetRenderViewHost(), "hasRefilled('firstname_form2')", &has_refilled));
ASSERT_TRUE(has_refilled);
// Make sure the new form was filled correctly.
ExpectFieldValue("firstname_form2", "Milton");
ExpectFieldValue("address_form2", "4120 Freidrich Lane");
ExpectFieldValue("state_form2", "TX");
ExpectFieldValue("city_form2", "Austin");
ExpectFieldValue("company_form2", "Initech");
ExpectFieldValue("email_form2", "red.swingline@initech.com");
ExpectFieldValue("phone_form2", "15125551234");
} }
// Test that forms that dynamically change a second time do not get filled. // Test that forms that dynamically change a second time do not get filled.
...@@ -2079,22 +2125,22 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest, ...@@ -2079,22 +2125,22 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/double_dynamic_form.html"); "a.com", "/autofill/double_dynamic_form.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url)); ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill(); TriggerFormFill("firstname");
// Wait for two dynamic changes to happen. // Wait for two dynamic changes to happen.
bool has_refilled = false; bool has_refilled = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool( ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetRenderViewHost(), "hasFinished()", &has_refilled)); GetRenderViewHost(), "hasRefilled()", &has_refilled));
ASSERT_FALSE(has_refilled); ASSERT_FALSE(has_refilled);
// Make sure the new form was not filled. // Make sure the new form was not filled.
ExpectFieldValue("firstname", ""); ExpectFieldValue("firstname_form2", "");
ExpectFieldValue("address1", ""); ExpectFieldValue("address_form2", "");
ExpectFieldValue("state", "CA"); // Default value. ExpectFieldValue("state_form2", "CA"); // Default value.
ExpectFieldValue("city", ""); ExpectFieldValue("city_form2", "");
ExpectFieldValue("company", ""); ExpectFieldValue("company_form2", "");
ExpectFieldValue("email", ""); ExpectFieldValue("email_form2", "");
ExpectFieldValue("phone", ""); ExpectFieldValue("phone_form2", "");
} }
// Test that forms that dynamically change after a second do not get filled. // Test that forms that dynamically change after a second do not get filled.
...@@ -2106,7 +2152,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest, ...@@ -2106,7 +2152,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/dynamic_form_after_delay.html"); "a.com", "/autofill/dynamic_form_after_delay.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url)); ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill(); TriggerFormFill("firstname");
// Wait for the dynamic change to happen. // Wait for the dynamic change to happen.
bool has_refilled = false; bool has_refilled = false;
...@@ -2115,13 +2161,13 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest, ...@@ -2115,13 +2161,13 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
ASSERT_FALSE(has_refilled); ASSERT_FALSE(has_refilled);
// Make sure that the new form was not filled. // Make sure that the new form was not filled.
ExpectFieldValue("firstname", ""); ExpectFieldValue("firstname_form1", "");
ExpectFieldValue("address1", ""); ExpectFieldValue("address_form1", "");
ExpectFieldValue("state", "CA"); // Default value. ExpectFieldValue("state_form1", "CA"); // Default value.
ExpectFieldValue("city", ""); ExpectFieldValue("city_form1", "");
ExpectFieldValue("company", ""); ExpectFieldValue("company_form1", "");
ExpectFieldValue("email", ""); ExpectFieldValue("email_form1", "");
ExpectFieldValue("phone", ""); ExpectFieldValue("phone_form1", "");
} }
// Test that only field of a type group that was filled initially get refilled. // Test that only field of a type group that was filled initially get refilled.
...@@ -2133,7 +2179,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest, ...@@ -2133,7 +2179,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/dynamic_form_new_field_types.html"); "a.com", "/autofill/dynamic_form_new_field_types.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url)); ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill(); TriggerFormFill("firstname");
// Wait for the dynamic change to happen. // Wait for the dynamic change to happen.
bool has_refilled = false; bool has_refilled = false;
...@@ -2142,19 +2188,19 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest, ...@@ -2142,19 +2188,19 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
ASSERT_TRUE(has_refilled); ASSERT_TRUE(has_refilled);
// The fields present in the initial fill should be filled. // The fields present in the initial fill should be filled.
ExpectFieldValue("firstname", "Milton"); ExpectFieldValue("firstname_form1", "Milton");
ExpectFieldValue("address1", "4120 Freidrich Lane"); ExpectFieldValue("address_form1", "4120 Freidrich Lane");
ExpectFieldValue("state", "TX"); ExpectFieldValue("state_form1", "TX");
ExpectFieldValue("city", "Austin"); ExpectFieldValue("city_form1", "Austin");
// Fields from group that were not present in the initial fill should not be // Fields from group that were not present in the initial fill should not be
// filled // filled
ExpectFieldValue("company", ""); ExpectFieldValue("company_form1", "");
// Fields that were present but hidden in the initial fill should not be // Fields that were present but hidden in the initial fill should not be
// filled. // filled.
ExpectFieldValue("email", ""); ExpectFieldValue("email_form1", "");
// The phone should be filled even if it's a different format than the initial // The phone should be filled even if it's a different format than the initial
// fill. // fill.
ExpectFieldValue("phone", "5125551234"); ExpectFieldValue("phone_form1", "5125551234");
} }
// Test that credit card fields are never re-filled. // Test that credit card fields are never re-filled.
...@@ -2202,7 +2248,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest, ...@@ -2202,7 +2248,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/dynamic_form_select_options_change.html"); "a.com", "/autofill/dynamic_form_select_options_change.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url)); ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill(); TriggerFormFill("firstname");
// Wait for the re-fill to happen. // Wait for the re-fill to happen.
bool has_refilled = false; bool has_refilled = false;
...@@ -2230,17 +2276,17 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest, ...@@ -2230,17 +2276,17 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/dynamic_form_double_select_options_change.html"); "a.com", "/autofill/dynamic_form_double_select_options_change.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url)); ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill(); TriggerFormFill("firstname");
// Wait for the re-fill to happen. // Wait for the re-fill to happen.
bool has_refilled = false; bool has_refilled = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool( ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetRenderViewHost(), "hasRefilled()", &has_refilled)); GetRenderViewHost(), "hasRefilled()", &has_refilled));
ASSERT_TRUE(has_refilled); ASSERT_FALSE(has_refilled);
// The fields that were initially filled and not reset should still be filled. // The fields that were initially filled and not reset should still be filled.
ExpectFieldValue("firstname", "Milton"); ExpectFieldValue("firstname", ""); // That field value was reset dynamically.
ExpectFieldValue("address1", ""); // That field value was reset dynamically. ExpectFieldValue("address1", "4120 Freidrich Lane");
ExpectFieldValue("state", "CA"); // Default value. ExpectFieldValue("state", "CA"); // Default value.
ExpectFieldValue("city", "Austin"); ExpectFieldValue("city", "Austin");
ExpectFieldValue("company", "Initech"); ExpectFieldValue("company", "Initech");
......
...@@ -30,34 +30,38 @@ ...@@ -30,34 +30,38 @@
<script src="dynamic_form_utils.js"></script> <script src="dynamic_form_utils.js"></script>
<script> <script>
var refilled = false; var notify_on_first_name_input_appear = false;
var callback_c = false;
function CountryChanged() { function CountryChanged() {
DynamicallyChangeForm(); RemoveForm('form1');
var new_form = AddNewFormAndFields('form1', 'addr1.1');
// Prepare the function to trigger a second dynamic form change.
document.getElementById("email").addEventListener('change', function(){
// Trigger a second form change on refill.
new_form.elements[0].addEventListener('change', function(){
RemoveForm("form1") RemoveForm("form1")
var new_form2 = AddNewFormAndFields("form2", "addr1.2");
var new_form2 = AddNewFormAndFields();
new_form2.setAttribute('id', "form2");
document.getElementsByTagName('body')[0].appendChild(new_form2); document.getElementsByTagName('body')[0].appendChild(new_form2);
if (callback_c) if (notify_on_first_name_input_appear) {
window.domAutomationController.send(document.getElementById("firstname").value != ''); // Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(document.getElementById("firstname_form2").value != '');
}, 1000);
}
}); });
document.getElementsByTagName('body')[0].appendChild(new_form);
} }
function hasFinished() { function hasRefilled() {
var first_name_input = document.getElementById("firstname"); var first_name_input = document.getElementById('firstname_form2');
if (refilled) { if (first_name_input) {
window.domAutomationController.send(document.getElementById("firstname").value != ''); // Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(first_name_input.value != '');
}, 1000);
} else { } else {
callback_c = true; notify_on_first_name_input_appear = true;
} }
} }
......
...@@ -29,21 +29,31 @@ ...@@ -29,21 +29,31 @@
<script src="dynamic_form_utils.js"></script> <script src="dynamic_form_utils.js"></script>
<script> <script>
var filled = false;
var notify_on_first_name_input_change = false;
var first_name_input_changed = false;
function CountryChanged() { function CountryChanged() {
DynamicallyChangeForm(); RemoveForm('form1');
filled = true; var new_form = AddNewFormAndFields('form1', 'addr1.1');
var first_name_input = new_form.elements[0];
first_name_input.onchange = function() {
if (notify_on_first_name_input_change)
window.domAutomationController.send(first_name_input.value != '');
else
first_name_input_changed = true;
}
document.getElementsByTagName('body')[0].appendChild(new_form);
} }
function hasRefilled() { function hasRefilled() {
var first_name_input = document.getElementById("firstname"); var first_name_input = document.getElementById('firstname_form1');
if (!filled || first_name_input.value == '') { if (first_name_input && first_name_input_changed) {
first_name_input.onchange = function() { window.domAutomationController.send(first_name_input.value != '');
window.domAutomationController.send(document.getElementById("firstname").value != '');
}
} else { } else {
window.domAutomationController.send(document.getElementById("firstname").value != ''); notify_on_first_name_input_change = true;
} }
} }
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
<script src="dynamic_form_utils.js"></script> <script src="dynamic_form_utils.js"></script>
<script> <script>
var filled = false; var notify_on_first_name_input_appear = false;
function sleep (time) { function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time)); return new Promise((resolve) => setTimeout(resolve, time));
...@@ -37,16 +37,29 @@ function sleep (time) { ...@@ -37,16 +37,29 @@ function sleep (time) {
function CountryChanged() { function CountryChanged() {
sleep(2000).then(() => { sleep(2000).then(() => {
DynamicallyChangeForm(); RemoveForm('form1');
filled = true; var new_form = AddNewFormAndFields('form1', 'addr1.1');
document.getElementsByTagName('body')[0].appendChild(new_form);
if (notify_on_first_name_input_appear) {
// Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(document.getElementById("firstname_form1").value != '');
}, 1000);
}
});
}
function hasRefilled() {
var first_name_input = document.getElementById('firstname_form1');
if (first_name_input && first_name_input_changed) {
// Give time to see if it fills. // Give time to see if it fills.
setTimeout(function() { setTimeout(function() {
window.domAutomationController.send(document.getElementById("firstname").value != ''); window.domAutomationController.send(first_name_input.value != '');
}, 1000);}); }, 1000);
} else {
notify_on_first_name_input_appear = true;
}
} }
// The callback will happen after the dynamic change.
function hasFinished() {}
</script> </script>
...@@ -38,7 +38,8 @@ ...@@ -38,7 +38,8 @@
<script src="dynamic_form_utils.js"></script> <script src="dynamic_form_utils.js"></script>
<script> <script>
var filled = false;
var notify_on_credit_card_name_input_appear = false;
function ExpChanged() { function ExpChanged() {
RemoveForm("cc1"); RemoveForm("cc1");
...@@ -99,14 +100,23 @@ function ExpChanged() { ...@@ -99,14 +100,23 @@ function ExpChanged() {
document.getElementsByTagName('body')[0].appendChild(new_form); document.getElementsByTagName('body')[0].appendChild(new_form);
filled = true; if (notify_on_credit_card_name_input_appear) {
// Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(document.getElementById("cc-name").value != '');
}, 1000);
}
}
// Give time to see if it fills. function hasRefilled() {
setTimeout(function() { var credit_card_name_input = document.getElementById('cc-name');
window.domAutomationController.send(document.getElementById("cc-name").value != ''); if (credit_card_name_input) {
}, 1000); setTimeout(function() {
window.domAutomationController.send(credit_card_name_input.value != '');
}, 1000);
} else {
notify_on_credit_card_name_input_appear = true;
}
} }
// The callback will happen after the dynamic change
function hasRefilled() {}
</script> </script>
\ No newline at end of file
...@@ -29,21 +29,31 @@ ...@@ -29,21 +29,31 @@
<script src="dynamic_form_utils.js"></script> <script src="dynamic_form_utils.js"></script>
<script> <script>
var filled = false;
var callback_c = false; var notify_on_first_name_input_appear = false;
function CountryChanged() { function CountryChanged() {
DynamicallyChangeForm(); RemoveForm('form1');
filled = true; var new_form = AddNewFormAndFields('form1', 'addr1.1');
if (callback_c) document.getElementsByTagName('body')[0].appendChild(new_form);
window.domAutomationController.send(document.getElementById("firstname").value != '');
if (notify_on_first_name_input_appear) {
// Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(document.getElementById("firstname_form1").value != '');
}, 1000);
}
} }
function hasRefilled() { function hasRefilled() {
if (filled) { var first_name_input = document.getElementById('firstname_form1');
window.domAutomationController.send(document.getElementById("firstname").value != ''); if (first_name_input) {
// Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(first_name_input.value != '');
}, 1000);
} else { } else {
callback_c = true; notify_on_first_name_input_appear = true;
} }
} }
......
...@@ -24,7 +24,38 @@ ...@@ -24,7 +24,38 @@
<script> <script>
var notify_on_first_name_input_change = false;
var first_name_input_changed = false;
function CountryChanged() { function CountryChanged() {
var first_name_input = document.getElementById("firstname");
// Reset the value of the first name field.
first_name_input.value = '';
first_name_input.addEventListener('change', function() {
first_name_input.value = '';
first_name_input_changed = true;
setTimeout(function() {
// Re-change the select options.
for (var i=0; i<state_select.length; i++) {
state_select.remove(i);
}
state_select.options[0] = new Option('CA', 'CA');
state_select.options[1] = new Option('MA', 'MA');
state_select.options[2] = new Option('TX', 'TX');
});
if (notify_on_first_name_input_change) {
// Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(first_name_input.value != '');
}, 1000);
}
});
// Reset the value of the address field.
document.getElementById("address1").value = '';
// Get the select object. // Get the select object.
var state_select = document.getElementById("state"); var state_select = document.getElementById("state");
...@@ -37,26 +68,18 @@ function CountryChanged() { ...@@ -37,26 +68,18 @@ function CountryChanged() {
state_select.options[0] = new Option('WA', 'WA'); state_select.options[0] = new Option('WA', 'WA');
state_select.options[1] = new Option('MA', 'MA'); state_select.options[1] = new Option('MA', 'MA');
state_select.options[2] = new Option('TX', 'TX'); state_select.options[2] = new Option('TX', 'TX');
// Reset the value of a field.
document.getElementById("address1").value = '';
// Add event lister for state change.
state_select.addEventListener('change', function(){
// Re-change the select options.
for (var i=0; i<state_select.length; i++) {
state_select.remove(i);
}
state_select.options[0] = new Option('CA', 'CA');
state_select.options[1] = new Option('MA', 'MA');
state_select.options[2] = new Option('TX', 'TX');
document.getElementById("address1").value = '';
window.domAutomationController.send(document.getElementById("firstname").value != '');
});
} }
// The callback will happen after the dynamic change. function hasRefilled() {
function hasRefilled() {} var first_name_input = document.getElementById('firstname');
if (first_name_input && first_name_input_changed) {
// Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(document.getElementById("firstname").value != '');
}, 1000);
} else {
notify_on_first_name_input_change = true;
}
}
</script> </script>
...@@ -31,27 +31,34 @@ ...@@ -31,27 +31,34 @@
<script src="dynamic_form_utils.js"></script> <script src="dynamic_form_utils.js"></script>
<script> <script>
var filled = false;
var notify_on_first_name_input_change = false;
var first_name_input_changed = false;
function CountryChanged() { function CountryChanged() {
RemoveForm("form1"); RemoveForm('form1');
var new_form = AddNewFormAndFields(); var new_form = AddNewFormAndFields('form1', 'addr1.1');
// Set a different type of phone field. // Set a different type of phone field.
new_form.elements[6].setAttribute('autocomplete', 'tel-national'); new_form.elements[6].setAttribute('autocomplete', 'tel-national');
var first_name_input = new_form.elements[0];
first_name_input.onchange = function() {
if (notify_on_first_name_input_change)
window.domAutomationController.send(first_name_input.value != '');
else
first_name_input_changed = true;
}
document.getElementsByTagName('body')[0].appendChild(new_form); document.getElementsByTagName('body')[0].appendChild(new_form);
filled = true;
} }
function hasRefilled() { function hasRefilled() {
var first_name_input = document.getElementById("firstname"); var first_name_input = document.getElementById('firstname_form1');
if (!filled || first_name_input.value == '') { if (first_name_input && first_name_input_changed) {
first_name_input.onchange = function() { window.domAutomationController.send(first_name_input.value != '');
window.domAutomationController.send(document.getElementById("firstname").value != '');
}
} else { } else {
window.domAutomationController.send(document.getElementById("firstname").value != ''); notify_on_first_name_input_change = true;
} }
} }
......
...@@ -24,7 +24,23 @@ ...@@ -24,7 +24,23 @@
<script> <script>
var notify_on_first_name_input_change = false;
var first_name_input_changed = false;
function CountryChanged() { function CountryChanged() {
var first_name_input = document.getElementById("firstname");
// Reset the value of the first name field.
first_name_input.value = '';
first_name_input.onchange = function() {
if (notify_on_first_name_input_change)
window.domAutomationController.send(first_name_input.value != '');
else
first_name_input_changed = true;
}
// Reset the value of the address field.
document.getElementById("address1").value = '';
// Get the select object. // Get the select object.
var state_select = document.getElementById("state"); var state_select = document.getElementById("state");
...@@ -37,17 +53,15 @@ function CountryChanged() { ...@@ -37,17 +53,15 @@ function CountryChanged() {
state_select.options[0] = new Option('WA', 'WA'); state_select.options[0] = new Option('WA', 'WA');
state_select.options[1] = new Option('MA', 'MA'); state_select.options[1] = new Option('MA', 'MA');
state_select.options[2] = new Option('TX', 'TX'); state_select.options[2] = new Option('TX', 'TX');
// Reset the value of a field.
document.getElementById("address1").value = '';
// Add event lister for state change.
state_select.addEventListener('change', function(){
window.domAutomationController.send(document.getElementById("firstname").value != '');
});
} }
// The callback will happen after the dynamic change. function hasRefilled() {
function hasRefilled() {} var first_name_input = document.getElementById('firstname');
if (first_name_input && first_name_input_changed) {
window.domAutomationController.send(first_name_input.value != '');
} else {
notify_on_first_name_input_change = true;
}
}
</script> </script>
...@@ -4,13 +4,7 @@ ...@@ -4,13 +4,7 @@
* found in the LICENSE file. * found in the LICENSE file.
*/ */
function DynamicallyChangeForm() { /** Removes the initial form. */
RemoveForm('form1');
var new_form = AddNewFormAndFields();
document.getElementsByTagName('body')[0].appendChild(new_form);
}
//* Removes the initial form. */
function RemoveForm(form_id) { function RemoveForm(form_id) {
var initial_form = document.getElementById(form_id); var initial_form = document.getElementById(form_id);
initial_form.parentNode.removeChild(initial_form); initial_form.parentNode.removeChild(initial_form);
...@@ -19,30 +13,30 @@ function RemoveForm(form_id) { ...@@ -19,30 +13,30 @@ function RemoveForm(form_id) {
} }
/** Adds a new form and fields for the dynamic form. */ /** Adds a new form and fields for the dynamic form. */
function AddNewFormAndFields() { function AddNewFormAndFields(form_id, form_name) {
var new_form = document.createElement('form'); var new_form = document.createElement('form');
new_form.setAttribute('method', 'post'); new_form.setAttribute('method', 'post');
new_form.setAttribute('action', 'https://example.com/') new_form.setAttribute('action', 'https://example.com/')
new_form.setAttribute('name', 'addr1.1'); new_form.setAttribute('name', form_name);
new_form.setAttribute('id', 'form1'); new_form.setAttribute('id', form_id);
var i = document.createElement('input'); var i = document.createElement('input');
i.setAttribute('type', 'text'); i.setAttribute('type', 'text');
i.setAttribute('name', 'firstname'); i.setAttribute('name', 'firstname');
i.setAttribute('id', 'firstname'); i.setAttribute('id', 'firstname_' + form_id);
i.setAttribute('autocomplete', 'given-name'); i.setAttribute('autocomplete', 'given-name');
new_form.appendChild(i); new_form.appendChild(i);
i = document.createElement('input'); i = document.createElement('input');
i.setAttribute('type', 'text'); i.setAttribute('type', 'text');
i.setAttribute('name', 'address1'); i.setAttribute('name', 'address1');
i.setAttribute('id', 'address1'); i.setAttribute('id', 'address_' + form_id);
i.setAttribute('autocomplete', 'address-line1'); i.setAttribute('autocomplete', 'address-line1');
new_form.appendChild(i); new_form.appendChild(i);
i = document.createElement('select'); i = document.createElement('select');
i.setAttribute('name', 'state'); i.setAttribute('name', 'state');
i.setAttribute('id', 'state'); i.setAttribute('id', 'state_' + form_id);
i.setAttribute('autocomplete', 'region'); i.setAttribute('autocomplete', 'region');
i.options[0] = new Option('CA', 'CA'); i.options[0] = new Option('CA', 'CA');
i.options[1] = new Option('MA', 'MA'); i.options[1] = new Option('MA', 'MA');
...@@ -52,28 +46,28 @@ function AddNewFormAndFields() { ...@@ -52,28 +46,28 @@ function AddNewFormAndFields() {
i = document.createElement('input'); i = document.createElement('input');
i.setAttribute('type', 'text'); i.setAttribute('type', 'text');
i.setAttribute('name', 'city'); i.setAttribute('name', 'city');
i.setAttribute('id', 'city'); i.setAttribute('id', 'city_' + form_id);
i.setAttribute('autocomplete', 'locality'); i.setAttribute('autocomplete', 'locality');
new_form.appendChild(i); new_form.appendChild(i);
i = document.createElement('input'); i = document.createElement('input');
i.setAttribute('type', 'text'); i.setAttribute('type', 'text');
i.setAttribute('name', 'company'); i.setAttribute('name', 'company');
i.setAttribute('id', 'company'); i.setAttribute('id', 'company_' + form_id);
i.setAttribute('autocomplete', 'organization'); i.setAttribute('autocomplete', 'organization');
new_form.appendChild(i); new_form.appendChild(i);
i = document.createElement('input'); i = document.createElement('input');
i.setAttribute('type', 'text'); i.setAttribute('type', 'text');
i.setAttribute('name', 'email'); i.setAttribute('name', 'email');
i.setAttribute('id', 'email'); i.setAttribute('id', 'email_' + form_id);
i.setAttribute('autocomplete', 'email'); i.setAttribute('autocomplete', 'email');
new_form.appendChild(i); new_form.appendChild(i);
i = document.createElement('input'); i = document.createElement('input');
i.setAttribute('type', 'text'); i.setAttribute('type', 'text');
i.setAttribute('name', 'phone'); i.setAttribute('name', 'phone');
i.setAttribute('id', 'phone'); i.setAttribute('id', 'phone_' + form_id);
i.setAttribute('autocomplete', 'tel'); i.setAttribute('autocomplete', 'tel');
new_form.appendChild(i); new_form.appendChild(i);
......
<!-- A page that is used to test that a dynamic form fill feature works properly. -->
<body>
<form name="addr1.1" id="form1" action="https://example.com/" method="post">
Name: <input type="text" name="firstname" id="firstname_form1"><br>
Address: <input type="text" name="address1" id="address_form1"><br>
City: <input type="text" name="city" id="city_form1"><br>
State: <select name="state" id="state1_form1">
<option value="CA">CA</option>
<option value="MA">MA</option>
<option value="NY">NY</option>
<option value="MD">MD</option>
<option value="OR">OR</option>
<option value="OH">OH</option>
<option value="IL">IL</option>
<option value="DC">DC</option>
</select> <br>
Zip: <input name="zip" id="zip_form1"> <br>
Country: <select name="country" id="country_form1" onchange="CountryChanged(this)">
<option value="CA">Canada</option>
<option value="US">United States</option>
</select> <br>
Company: <input name="company" id="company_form1"> <br>
Email: <input name="email" id="email_form1"> <br>
Phone: <input name="phone" id="phone_form1"> <br>
<input type="reset" value="Reset">
<input type="submit" value="Submit" id="profile_submit_form1">
</form>
<form name="addr1.2" id="form2" action="https://example.com/" method="post">
Name: <input type="text" name="firstname" id="firstname_form2"><br>
Address: <input type="text" name="address1" id="address_form2"><br>
City: <input type="text" name="city" id="city_form2"><br>
State: <select name="state" id="state_form2">
<option value="CA">CA</option>
<option value="MA">MA</option>
<option value="NY">NY</option>
<option value="MD">MD</option>
<option value="OR">OR</option>
<option value="OH">OH</option>
<option value="IL">IL</option>
<option value="DC">DC</option>
</select> <br>
Zip: <input name="zip" id="zip_form2"> <br>
Country: <select name="country" id="country_form2" onchange="CountryChanged(this)">
<option value="CA">Canada</option>
<option value="US">United States</option>
</select> <br>
Company: <input name="company" id="company_form2"> <br>
Email: <input name="email" id="email_form2"> <br>
Phone: <input name="phone" id="phone_form2"> <br>
<input type="reset" value="Reset">
<input type="submit" value="Submit" id="profile_submit_form2">
</form>
</body>
<script src="dynamic_form_utils.js"></script>
<script>
var notify_on_first_name_input_change = {};
var first_name_input_changed = {};
function CountryChanged(select_element) {
var form_id = select_element.form.id;
var form_name = select_element.form.name;
RemoveForm(form_id);
var new_form = AddNewFormAndFields(form_id, form_name);
var first_name_input_id = 'firstname_' + form_id;
var first_name_input = new_form.elements[0];
first_name_input.onchange = function() {
if (notify_on_first_name_input_change[first_name_input_id])
window.domAutomationController.send(first_name_input.value != '');
else
first_name_input_changed[first_name_input_id] = true;
}
document.getElementsByTagName('body')[0].appendChild(new_form);
}
function hasRefilled(first_name_input_id) {
var first_name_input = document.getElementById(first_name_input_id);
if (first_name_input && first_name_input_changed[first_name_input_id]) {
window.domAutomationController.send(first_name_input.value != '');
} else {
notify_on_first_name_input_change[first_name_input_id] = true;
}
}
</script>
...@@ -203,13 +203,6 @@ AutofillManager::FillingContext::FillingContext() = default; ...@@ -203,13 +203,6 @@ AutofillManager::FillingContext::FillingContext() = default;
AutofillManager::FillingContext::~FillingContext() = default; AutofillManager::FillingContext::~FillingContext() = default;
void AutofillManager::FillingContext::Reset() {
attempted_refill = false;
filled_form_name.clear();
filled_field_name.clear();
type_groups_to_refill.clear();
}
AutofillManager::AutofillManager( AutofillManager::AutofillManager(
AutofillDriver* driver, AutofillDriver* driver,
AutofillClient* client, AutofillClient* client,
...@@ -774,12 +767,15 @@ void AutofillManager::FillOrPreviewProfileForm( ...@@ -774,12 +767,15 @@ void AutofillManager::FillOrPreviewProfileForm(
address_form_event_logger_->OnDidFillSuggestion( address_form_event_logger_->OnDidFillSuggestion(
profile, form_structure->form_parsed_timestamp()); profile, form_structure->form_parsed_timestamp());
// Set up the information needed for an eventual refill. // Set up the information needed for an eventual refill of this form.
if (base::FeatureList::IsEnabled(features::kAutofillDynamicForms)) { if (base::FeatureList::IsEnabled(features::kAutofillDynamicForms) &&
filling_context_.temp_data_model = profile; !form_structure->form_name().empty()) {
filling_context_.filled_form_name = form_structure->form_name(); auto& entry = filling_contexts_map_[form_structure->form_name()];
filling_context_.filled_field_name = autofill_field->unique_name(); auto filling_context = std::make_unique<FillingContext>();
filling_context_.original_fill_time = base::TimeTicks::Now(); filling_context->temp_data_model = profile;
filling_context->filled_field_name = autofill_field->unique_name();
filling_context->original_fill_time = base::TimeTicks::Now();
entry = std::move(filling_context);
} }
} }
...@@ -1213,7 +1209,7 @@ void AutofillManager::Reset() { ...@@ -1213,7 +1209,7 @@ void AutofillManager::Reset() {
forms_loaded_timestamps_.clear(); forms_loaded_timestamps_.clear();
initial_interaction_timestamp_ = TimeTicks(); initial_interaction_timestamp_ = TimeTicks();
external_delegate_->Reset(); external_delegate_->Reset();
filling_context_.Reset(); filling_contexts_map_.clear();
} }
AutofillManager::AutofillManager( AutofillManager::AutofillManager(
...@@ -1363,12 +1359,18 @@ void AutofillManager::FillOrPreviewDataModelForm( ...@@ -1363,12 +1359,18 @@ void AutofillManager::FillOrPreviewDataModelForm(
// Only record the types that are filled for an eventual refill if all the // Only record the types that are filled for an eventual refill if all the
// following are satisfied: // following are satisfied:
// The refilling feature is enabled. // The refilling feature is enabled.
// A refill has not been attempted yet. // A form with the given name is already filled.
// A refill has not been attempted for that form yet.
// This fill is not a refill attempt. // This fill is not a refill attempt.
// This is not a credit card fill. // This is not a credit card fill.
FillingContext* filling_context = nullptr;
auto itr = filling_contexts_map_.find(form_structure->form_name());
if (itr != filling_contexts_map_.end())
filling_context = itr->second.get();
bool could_attempt_refill = bool could_attempt_refill =
base::FeatureList::IsEnabled(features::kAutofillDynamicForms) && base::FeatureList::IsEnabled(features::kAutofillDynamicForms) &&
!filling_context_.attempted_refill && !is_refill && !is_credit_card; filling_context != nullptr && !filling_context->attempted_refill &&
!is_refill && !is_credit_card;
for (size_t i = 0; i < form_structure->field_count(); ++i) { for (size_t i = 0; i < form_structure->field_count(); ++i) {
if (form_structure->field(i)->section() != autofill_field->section()) if (form_structure->field(i)->section() != autofill_field->section())
...@@ -1394,17 +1396,21 @@ void AutofillManager::FillOrPreviewDataModelForm( ...@@ -1394,17 +1396,21 @@ void AutofillManager::FillOrPreviewDataModelForm(
if (cached_field->role == FormFieldData::ROLE_ATTRIBUTE_PRESENTATION) if (cached_field->role == FormFieldData::ROLE_ATTRIBUTE_PRESENTATION)
continue; continue;
// Don't fill previously autofilled fields except the initiating field. // Don't fill previously autofilled fields except the initiating field or
if (result.fields[i].is_autofilled && !cached_field->SameFieldAs(field)) // when it's a refill
if (result.fields[i].is_autofilled && !cached_field->SameFieldAs(field) &&
!is_refill) {
continue; continue;
}
if (field_group_type == NO_GROUP) if (field_group_type == NO_GROUP)
continue; continue;
// On a refill, only fill fields from type groups that were present during // On a refill, only fill fields from type groups that were present during
// the initial fill. // the initial fill.
if (is_refill && !base::ContainsKey(filling_context_.type_groups_to_refill, if (is_refill &&
field_group_type)) { !base::ContainsKey(filling_context->type_groups_originally_filled,
field_group_type)) {
continue; continue;
} }
...@@ -1416,7 +1422,7 @@ void AutofillManager::FillOrPreviewDataModelForm( ...@@ -1416,7 +1422,7 @@ void AutofillManager::FillOrPreviewDataModelForm(
} }
if (could_attempt_refill) if (could_attempt_refill)
filling_context_.type_groups_to_refill.insert(field_group_type); filling_context->type_groups_originally_filled.insert(field_group_type);
// Must match ForEachMatchingFormField() in form_autofill_util.cc. // Must match ForEachMatchingFormField() in form_autofill_util.cc.
// Only notify autofilling of empty fields and the field that initiated // Only notify autofilling of empty fields and the field that initiated
...@@ -1670,14 +1676,19 @@ void AutofillManager::ParseForms(const std::vector<FormData>& forms) { ...@@ -1670,14 +1676,19 @@ void AutofillManager::ParseForms(const std::vector<FormData>& forms) {
parse_form_start_time); parse_form_start_time);
// If a form with the same name was previously filled, and there has not // If a form with the same name was previously filled, and there has not
// been a refill attempt yet, start the process of triggering a refill. // been a refill attempt on that form yet, start the process of triggering a
// refill.
if (ShouldTriggerRefill(*form_structure)) { if (ShouldTriggerRefill(*form_structure)) {
auto itr = filling_contexts_map_.find(form_structure->form_name());
DCHECK(itr != filling_contexts_map_.end());
FillingContext* filling_context = itr->second.get();
// If a timer for the refill was already running, it means the form // If a timer for the refill was already running, it means the form
// changed again. Stop the timer and start it again. // changed again. Stop the timer and start it again.
if (filling_context_.on_refill_timer.IsRunning()) if (filling_context->on_refill_timer.IsRunning())
filling_context_.on_refill_timer.AbandonAndStop(); filling_context->on_refill_timer.AbandonAndStop();
filling_context_.on_refill_timer.Start( filling_context->on_refill_timer.Start(
FROM_HERE, FROM_HERE,
base::TimeDelta::FromMilliseconds(kWaitTimeForDynamicFormsMs), base::TimeDelta::FromMilliseconds(kWaitTimeForDynamicFormsMs),
base::BindRepeating(&AutofillManager::TriggerRefill, base::BindRepeating(&AutofillManager::TriggerRefill,
...@@ -2013,22 +2024,32 @@ bool AutofillManager::ShouldTriggerRefill(const FormStructure& form_structure) { ...@@ -2013,22 +2024,32 @@ bool AutofillManager::ShouldTriggerRefill(const FormStructure& form_structure) {
if (!base::FeatureList::IsEnabled(features::kAutofillDynamicForms)) if (!base::FeatureList::IsEnabled(features::kAutofillDynamicForms))
return false; return false;
// Should not refill if a form with the same name has not been filled before.
auto itr = filling_contexts_map_.find(form_structure.form_name());
if (itr == filling_contexts_map_.end())
return false;
FillingContext* filling_context = itr->second.get();
base::TimeTicks now = base::TimeTicks::Now(); base::TimeTicks now = base::TimeTicks::Now();
base::TimeDelta delta = now - filling_context_.original_fill_time; base::TimeDelta delta = now - filling_context->original_fill_time;
return !filling_context_.attempted_refill && return !filling_context->attempted_refill &&
!filling_context_.filled_form_name.empty() &&
filling_context_.filled_form_name == form_structure.form_name() &&
delta.InMilliseconds() < kLimitBeforeRefillMs; delta.InMilliseconds() < kLimitBeforeRefillMs;
} }
void AutofillManager::TriggerRefill(const FormData& form, void AutofillManager::TriggerRefill(const FormData& form,
FormStructure* form_structure) { FormStructure* form_structure) {
filling_context_.attempted_refill = true; auto itr = filling_contexts_map_.find(form_structure->form_name());
DCHECK(itr != filling_contexts_map_.end());
FillingContext* filling_context = itr->second.get();
DCHECK(!filling_context->attempted_refill);
filling_context->attempted_refill = true;
// Try to find the field from which the original field originated. // Try to find the field from which the original field originated.
AutofillField* autofill_field = nullptr; AutofillField* autofill_field = nullptr;
for (const std::unique_ptr<AutofillField>& field : *form_structure) { for (const std::unique_ptr<AutofillField>& field : *form_structure) {
if (field->unique_name() == filling_context_.filled_field_name) { if (field->unique_name() == filling_context->filled_field_name) {
autofill_field = field.get(); autofill_field = field.get();
break; break;
} }
...@@ -2042,7 +2063,7 @@ void AutofillManager::TriggerRefill(const FormData& form, ...@@ -2042,7 +2063,7 @@ void AutofillManager::TriggerRefill(const FormData& form,
base::string16 cvc; base::string16 cvc;
FillOrPreviewDataModelForm( FillOrPreviewDataModelForm(
AutofillDriver::RendererFormDataAction::FORM_DATA_ACTION_FILL, AutofillDriver::RendererFormDataAction::FORM_DATA_ACTION_FILL,
/*query_id=*/-1, form, field, filling_context_.temp_data_model, /*query_id=*/-1, form, field, filling_context->temp_data_model,
/*is_credit_card=*/false, cvc, form_structure, autofill_field, /*is_credit_card=*/false, cvc, form_structure, autofill_field,
/*is_refill=*/true); /*is_refill=*/true);
} }
......
...@@ -289,18 +289,15 @@ class AutofillManager : public AutofillHandler, ...@@ -289,18 +289,15 @@ class AutofillManager : public AutofillHandler,
} }
private: private:
// Keeps track of the filling context for a form, used to make refill attemps.
struct FillingContext { struct FillingContext {
FillingContext(); FillingContext();
~FillingContext(); ~FillingContext();
void Reset(); // Whether a refill attempt was made.
// Whether a refill attempts was made on that page.
bool attempted_refill = false; bool attempted_refill = false;
// The profile that was used for the initial fill. // The profile that was used for the initial fill.
AutofillProfile temp_data_model; AutofillProfile temp_data_model;
// The name of the form that was initially filled.
base::string16 filled_form_name;
// The name of the field that was initially filled. // The name of the field that was initially filled.
base::string16 filled_field_name; base::string16 filled_field_name;
// The time at which the initial fill occured. // The time at which the initial fill occured.
...@@ -308,7 +305,7 @@ class AutofillManager : public AutofillHandler, ...@@ -308,7 +305,7 @@ class AutofillManager : public AutofillHandler,
// The timer used to trigger a refill. // The timer used to trigger a refill.
base::OneShotTimer on_refill_timer; base::OneShotTimer on_refill_timer;
// The field type groups that were initially filled. // The field type groups that were initially filled.
std::set<FieldTypeGroup> type_groups_to_refill; std::set<FieldTypeGroup> type_groups_originally_filled;
}; };
// AutofillDownloadManager::Observer: // AutofillDownloadManager::Observer:
...@@ -592,8 +589,10 @@ class AutofillManager : public AutofillHandler, ...@@ -592,8 +589,10 @@ class AutofillManager : public AutofillHandler,
AutofillAssistant autofill_assistant_; AutofillAssistant autofill_assistant_;
#endif #endif
// Filling context used for dynamic fills. // A map of form names to FillingContext instances used to make refill
FillingContext filling_context_; // attempts for dynamic forms.
std::map<base::string16, std::unique_ptr<FillingContext>>
filling_contexts_map_;
base::WeakPtrFactory<AutofillManager> weak_ptr_factory_; base::WeakPtrFactory<AutofillManager> weak_ptr_factory_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment