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 {
ExpectFilledTestForm();
}
void TriggerFormFill() {
FocusFirstNameField();
void TriggerFormFill(const std::string& field_name) {
FocusFieldByName(field_name);
// Start filling the first name field with "M" and wait for the popup to be
// shown.
......@@ -1829,7 +1829,7 @@ IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, DynamicChangingFormFill) {
embedded_test_server()->GetURL("/autofill/dynamic_form_disabled.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill();
TriggerFormFill("firstname");
// Wait for the re-fill to happen.
bool has_refilled = false;
......@@ -1838,13 +1838,13 @@ IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, DynamicChangingFormFill) {
ASSERT_FALSE(has_refilled);
// Make sure that the new form was not filled.
ExpectFieldValue("firstname", "");
ExpectFieldValue("address1", "");
ExpectFieldValue("state", "CA"); // Default value.
ExpectFieldValue("city", "");
ExpectFieldValue("company", "");
ExpectFieldValue("email", "");
ExpectFieldValue("phone", "");
ExpectFieldValue("firstname_form1", "");
ExpectFieldValue("address_form1", "");
ExpectFieldValue("state_form1", "CA"); // Default value.
ExpectFieldValue("city_form1", "");
ExpectFieldValue("company_form1", "");
ExpectFieldValue("email_form1", "");
ExpectFieldValue("phone_form1", "");
}
// An extension of the test fixture for tests with site isolation.
......@@ -2052,7 +2052,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
embedded_test_server()->GetURL("a.com", "/autofill/dynamic_form.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill();
TriggerFormFill("firstname");
// Wait for the re-fill to happen.
bool has_refilled = false;
......@@ -2061,13 +2061,59 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
ASSERT_TRUE(has_refilled);
// Make sure the new form was filled correctly.
ExpectFieldValue("firstname", "Milton");
ExpectFieldValue("address1", "4120 Freidrich Lane");
ExpectFieldValue("state", "TX");
ExpectFieldValue("city", "Austin");
ExpectFieldValue("company", "Initech");
ExpectFieldValue("email", "red.swingline@initech.com");
ExpectFieldValue("phone", "15125551234");
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");
}
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.
......@@ -2079,22 +2125,22 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/double_dynamic_form.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill();
TriggerFormFill("firstname");
// Wait for two dynamic changes to happen.
bool has_refilled = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
GetRenderViewHost(), "hasFinished()", &has_refilled));
GetRenderViewHost(), "hasRefilled()", &has_refilled));
ASSERT_FALSE(has_refilled);
// Make sure the new form was not filled.
ExpectFieldValue("firstname", "");
ExpectFieldValue("address1", "");
ExpectFieldValue("state", "CA"); // Default value.
ExpectFieldValue("city", "");
ExpectFieldValue("company", "");
ExpectFieldValue("email", "");
ExpectFieldValue("phone", "");
ExpectFieldValue("firstname_form2", "");
ExpectFieldValue("address_form2", "");
ExpectFieldValue("state_form2", "CA"); // Default value.
ExpectFieldValue("city_form2", "");
ExpectFieldValue("company_form2", "");
ExpectFieldValue("email_form2", "");
ExpectFieldValue("phone_form2", "");
}
// Test that forms that dynamically change after a second do not get filled.
......@@ -2106,7 +2152,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/dynamic_form_after_delay.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill();
TriggerFormFill("firstname");
// Wait for the dynamic change to happen.
bool has_refilled = false;
......@@ -2115,13 +2161,13 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
ASSERT_FALSE(has_refilled);
// Make sure that the new form was not filled.
ExpectFieldValue("firstname", "");
ExpectFieldValue("address1", "");
ExpectFieldValue("state", "CA"); // Default value.
ExpectFieldValue("city", "");
ExpectFieldValue("company", "");
ExpectFieldValue("email", "");
ExpectFieldValue("phone", "");
ExpectFieldValue("firstname_form1", "");
ExpectFieldValue("address_form1", "");
ExpectFieldValue("state_form1", "CA"); // Default value.
ExpectFieldValue("city_form1", "");
ExpectFieldValue("company_form1", "");
ExpectFieldValue("email_form1", "");
ExpectFieldValue("phone_form1", "");
}
// Test that only field of a type group that was filled initially get refilled.
......@@ -2133,7 +2179,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/dynamic_form_new_field_types.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill();
TriggerFormFill("firstname");
// Wait for the dynamic change to happen.
bool has_refilled = false;
......@@ -2142,19 +2188,19 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
ASSERT_TRUE(has_refilled);
// The fields present in the initial fill should be filled.
ExpectFieldValue("firstname", "Milton");
ExpectFieldValue("address1", "4120 Freidrich Lane");
ExpectFieldValue("state", "TX");
ExpectFieldValue("city", "Austin");
ExpectFieldValue("firstname_form1", "Milton");
ExpectFieldValue("address_form1", "4120 Freidrich Lane");
ExpectFieldValue("state_form1", "TX");
ExpectFieldValue("city_form1", "Austin");
// Fields from group that were not present in the initial fill should not be
// filled
ExpectFieldValue("company", "");
ExpectFieldValue("company_form1", "");
// Fields that were present but hidden in the initial fill should not be
// filled.
ExpectFieldValue("email", "");
ExpectFieldValue("email_form1", "");
// The phone should be filled even if it's a different format than the initial
// fill.
ExpectFieldValue("phone", "5125551234");
ExpectFieldValue("phone_form1", "5125551234");
}
// Test that credit card fields are never re-filled.
......@@ -2202,7 +2248,7 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/dynamic_form_select_options_change.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill();
TriggerFormFill("firstname");
// Wait for the re-fill to happen.
bool has_refilled = false;
......@@ -2230,17 +2276,17 @@ IN_PROC_BROWSER_TEST_F(DynamicFormInteractiveTest,
"a.com", "/autofill/dynamic_form_double_select_options_change.html");
ASSERT_NO_FATAL_FAILURE(ui_test_utils::NavigateToURL(browser(), url));
TriggerFormFill();
TriggerFormFill("firstname");
// Wait for the re-fill to happen.
bool has_refilled = false;
ASSERT_TRUE(content::ExecuteScriptAndExtractBool(
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.
ExpectFieldValue("firstname", "Milton");
ExpectFieldValue("address1", ""); // That field value was reset dynamically.
ExpectFieldValue("firstname", ""); // That field value was reset dynamically.
ExpectFieldValue("address1", "4120 Freidrich Lane");
ExpectFieldValue("state", "CA"); // Default value.
ExpectFieldValue("city", "Austin");
ExpectFieldValue("company", "Initech");
......
......@@ -30,34 +30,38 @@
<script src="dynamic_form_utils.js"></script>
<script>
var refilled = false;
var callback_c = false;
var notify_on_first_name_input_appear = false;
function CountryChanged() {
DynamicallyChangeForm();
// Prepare the function to trigger a second dynamic form change.
document.getElementById("email").addEventListener('change', function(){
RemoveForm('form1');
var new_form = AddNewFormAndFields('form1', 'addr1.1');
// Trigger a second form change on refill.
new_form.elements[0].addEventListener('change', function(){
RemoveForm("form1")
var new_form2 = AddNewFormAndFields();
new_form2.setAttribute('id', "form2");
var new_form2 = AddNewFormAndFields("form2", "addr1.2");
document.getElementsByTagName('body')[0].appendChild(new_form2);
if (callback_c)
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_form2").value != '');
}, 1000);
}
});
document.getElementsByTagName('body')[0].appendChild(new_form);
}
function hasFinished() {
var first_name_input = document.getElementById("firstname");
if (refilled) {
window.domAutomationController.send(document.getElementById("firstname").value != '');
function hasRefilled() {
var first_name_input = document.getElementById('firstname_form2');
if (first_name_input) {
// Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(first_name_input.value != '');
}, 1000);
} else {
callback_c = true;
notify_on_first_name_input_appear = true;
}
}
......
......@@ -29,21 +29,31 @@
<script src="dynamic_form_utils.js"></script>
<script>
var filled = false;
var notify_on_first_name_input_change = false;
var first_name_input_changed = false;
function CountryChanged() {
DynamicallyChangeForm();
filled = true;
RemoveForm('form1');
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() {
var first_name_input = document.getElementById("firstname");
if (!filled || first_name_input.value == '') {
first_name_input.onchange = function() {
window.domAutomationController.send(document.getElementById("firstname").value != '');
}
var first_name_input = document.getElementById('firstname_form1');
if (first_name_input && first_name_input_changed) {
window.domAutomationController.send(first_name_input.value != '');
} else {
window.domAutomationController.send(document.getElementById("firstname").value != '');
notify_on_first_name_input_change = true;
}
}
......
......@@ -29,7 +29,7 @@
<script src="dynamic_form_utils.js"></script>
<script>
var filled = false;
var notify_on_first_name_input_appear = false;
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
......@@ -37,16 +37,29 @@ function sleep (time) {
function CountryChanged() {
sleep(2000).then(() => {
DynamicallyChangeForm();
filled = true;
RemoveForm('form1');
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.
setTimeout(function() {
window.domAutomationController.send(document.getElementById("firstname").value != '');
}, 1000);});
window.domAutomationController.send(first_name_input.value != '');
}, 1000);
} else {
notify_on_first_name_input_appear = true;
}
}
// The callback will happen after the dynamic change.
function hasFinished() {}
</script>
......@@ -38,7 +38,8 @@
<script src="dynamic_form_utils.js"></script>
<script>
var filled = false;
var notify_on_credit_card_name_input_appear = false;
function ExpChanged() {
RemoveForm("cc1");
......@@ -99,14 +100,23 @@ function ExpChanged() {
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.
setTimeout(function() {
window.domAutomationController.send(document.getElementById("cc-name").value != '');
}, 1000);
function hasRefilled() {
var credit_card_name_input = document.getElementById('cc-name');
if (credit_card_name_input) {
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>
\ No newline at end of file
......@@ -29,21 +29,31 @@
<script src="dynamic_form_utils.js"></script>
<script>
var filled = false;
var callback_c = false;
var notify_on_first_name_input_appear = false;
function CountryChanged() {
DynamicallyChangeForm();
filled = true;
if (callback_c)
window.domAutomationController.send(document.getElementById("firstname").value != '');
RemoveForm('form1');
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() {
if (filled) {
window.domAutomationController.send(document.getElementById("firstname").value != '');
var first_name_input = document.getElementById('firstname_form1');
if (first_name_input) {
// Give time to see if it fills.
setTimeout(function() {
window.domAutomationController.send(first_name_input.value != '');
}, 1000);
} else {
callback_c = true;
notify_on_first_name_input_appear = true;
}
}
......
......@@ -24,7 +24,38 @@
<script>
var notify_on_first_name_input_change = false;
var first_name_input_changed = false;
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.
var state_select = document.getElementById("state");
......@@ -37,26 +68,18 @@ function CountryChanged() {
state_select.options[0] = new Option('WA', 'WA');
state_select.options[1] = new Option('MA', 'MA');
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>
......@@ -31,27 +31,34 @@
<script src="dynamic_form_utils.js"></script>
<script>
var filled = false;
var notify_on_first_name_input_change = false;
var first_name_input_changed = false;
function CountryChanged() {
RemoveForm("form1");
var new_form = AddNewFormAndFields();
RemoveForm('form1');
var new_form = AddNewFormAndFields('form1', 'addr1.1');
// Set a different type of phone field.
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);
filled = true;
}
function hasRefilled() {
var first_name_input = document.getElementById("firstname");
if (!filled || first_name_input.value == '') {
first_name_input.onchange = function() {
window.domAutomationController.send(document.getElementById("firstname").value != '');
}
var first_name_input = document.getElementById('firstname_form1');
if (first_name_input && first_name_input_changed) {
window.domAutomationController.send(first_name_input.value != '');
} else {
window.domAutomationController.send(document.getElementById("firstname").value != '');
notify_on_first_name_input_change = true;
}
}
......
......@@ -24,7 +24,23 @@
<script>
var notify_on_first_name_input_change = false;
var first_name_input_changed = false;
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.
var state_select = document.getElementById("state");
......@@ -37,17 +53,15 @@ function CountryChanged() {
state_select.options[0] = new Option('WA', 'WA');
state_select.options[1] = new Option('MA', 'MA');
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>
......@@ -4,13 +4,7 @@
* found in the LICENSE file.
*/
function DynamicallyChangeForm() {
RemoveForm('form1');
var new_form = AddNewFormAndFields();
document.getElementsByTagName('body')[0].appendChild(new_form);
}
//* Removes the initial form. */
/** Removes the initial form. */
function RemoveForm(form_id) {
var initial_form = document.getElementById(form_id);
initial_form.parentNode.removeChild(initial_form);
......@@ -19,30 +13,30 @@ function RemoveForm(form_id) {
}
/** Adds a new form and fields for the dynamic form. */
function AddNewFormAndFields() {
function AddNewFormAndFields(form_id, form_name) {
var new_form = document.createElement('form');
new_form.setAttribute('method', 'post');
new_form.setAttribute('action', 'https://example.com/')
new_form.setAttribute('name', 'addr1.1');
new_form.setAttribute('id', 'form1');
new_form.setAttribute('name', form_name);
new_form.setAttribute('id', form_id);
var i = document.createElement('input');
i.setAttribute('type', 'text');
i.setAttribute('name', 'firstname');
i.setAttribute('id', 'firstname');
i.setAttribute('id', 'firstname_' + form_id);
i.setAttribute('autocomplete', 'given-name');
new_form.appendChild(i);
i = document.createElement('input');
i.setAttribute('type', 'text');
i.setAttribute('name', 'address1');
i.setAttribute('id', 'address1');
i.setAttribute('id', 'address_' + form_id);
i.setAttribute('autocomplete', 'address-line1');
new_form.appendChild(i);
i = document.createElement('select');
i.setAttribute('name', 'state');
i.setAttribute('id', 'state');
i.setAttribute('id', 'state_' + form_id);
i.setAttribute('autocomplete', 'region');
i.options[0] = new Option('CA', 'CA');
i.options[1] = new Option('MA', 'MA');
......@@ -52,28 +46,28 @@ function AddNewFormAndFields() {
i = document.createElement('input');
i.setAttribute('type', 'text');
i.setAttribute('name', 'city');
i.setAttribute('id', 'city');
i.setAttribute('id', 'city_' + form_id);
i.setAttribute('autocomplete', 'locality');
new_form.appendChild(i);
i = document.createElement('input');
i.setAttribute('type', 'text');
i.setAttribute('name', 'company');
i.setAttribute('id', 'company');
i.setAttribute('id', 'company_' + form_id);
i.setAttribute('autocomplete', 'organization');
new_form.appendChild(i);
i = document.createElement('input');
i.setAttribute('type', 'text');
i.setAttribute('name', 'email');
i.setAttribute('id', 'email');
i.setAttribute('id', 'email_' + form_id);
i.setAttribute('autocomplete', 'email');
new_form.appendChild(i);
i = document.createElement('input');
i.setAttribute('type', 'text');
i.setAttribute('name', 'phone');
i.setAttribute('id', 'phone');
i.setAttribute('id', 'phone_' + form_id);
i.setAttribute('autocomplete', 'tel');
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;
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(
AutofillDriver* driver,
AutofillClient* client,
......@@ -774,12 +767,15 @@ void AutofillManager::FillOrPreviewProfileForm(
address_form_event_logger_->OnDidFillSuggestion(
profile, form_structure->form_parsed_timestamp());
// Set up the information needed for an eventual refill.
if (base::FeatureList::IsEnabled(features::kAutofillDynamicForms)) {
filling_context_.temp_data_model = profile;
filling_context_.filled_form_name = form_structure->form_name();
filling_context_.filled_field_name = autofill_field->unique_name();
filling_context_.original_fill_time = base::TimeTicks::Now();
// Set up the information needed for an eventual refill of this form.
if (base::FeatureList::IsEnabled(features::kAutofillDynamicForms) &&
!form_structure->form_name().empty()) {
auto& entry = filling_contexts_map_[form_structure->form_name()];
auto filling_context = std::make_unique<FillingContext>();
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() {
forms_loaded_timestamps_.clear();
initial_interaction_timestamp_ = TimeTicks();
external_delegate_->Reset();
filling_context_.Reset();
filling_contexts_map_.clear();
}
AutofillManager::AutofillManager(
......@@ -1363,12 +1359,18 @@ void AutofillManager::FillOrPreviewDataModelForm(
// Only record the types that are filled for an eventual refill if all the
// following are satisfied:
// 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 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 =
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) {
if (form_structure->field(i)->section() != autofill_field->section())
......@@ -1394,17 +1396,21 @@ void AutofillManager::FillOrPreviewDataModelForm(
if (cached_field->role == FormFieldData::ROLE_ATTRIBUTE_PRESENTATION)
continue;
// Don't fill previously autofilled fields except the initiating field.
if (result.fields[i].is_autofilled && !cached_field->SameFieldAs(field))
// Don't fill previously autofilled fields except the initiating field or
// when it's a refill
if (result.fields[i].is_autofilled && !cached_field->SameFieldAs(field) &&
!is_refill) {
continue;
}
if (field_group_type == NO_GROUP)
continue;
// On a refill, only fill fields from type groups that were present during
// the initial fill.
if (is_refill && !base::ContainsKey(filling_context_.type_groups_to_refill,
field_group_type)) {
if (is_refill &&
!base::ContainsKey(filling_context->type_groups_originally_filled,
field_group_type)) {
continue;
}
......@@ -1416,7 +1422,7 @@ void AutofillManager::FillOrPreviewDataModelForm(
}
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.
// Only notify autofilling of empty fields and the field that initiated
......@@ -1670,14 +1676,19 @@ void AutofillManager::ParseForms(const std::vector<FormData>& forms) {
parse_form_start_time);
// 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)) {
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
// changed again. Stop the timer and start it again.
if (filling_context_.on_refill_timer.IsRunning())
filling_context_.on_refill_timer.AbandonAndStop();
if (filling_context->on_refill_timer.IsRunning())
filling_context->on_refill_timer.AbandonAndStop();
filling_context_.on_refill_timer.Start(
filling_context->on_refill_timer.Start(
FROM_HERE,
base::TimeDelta::FromMilliseconds(kWaitTimeForDynamicFormsMs),
base::BindRepeating(&AutofillManager::TriggerRefill,
......@@ -2013,22 +2024,32 @@ bool AutofillManager::ShouldTriggerRefill(const FormStructure& form_structure) {
if (!base::FeatureList::IsEnabled(features::kAutofillDynamicForms))
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::TimeDelta delta = now - filling_context_.original_fill_time;
return !filling_context_.attempted_refill &&
!filling_context_.filled_form_name.empty() &&
filling_context_.filled_form_name == form_structure.form_name() &&
base::TimeDelta delta = now - filling_context->original_fill_time;
return !filling_context->attempted_refill &&
delta.InMilliseconds() < kLimitBeforeRefillMs;
}
void AutofillManager::TriggerRefill(const FormData& form,
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.
AutofillField* autofill_field = nullptr;
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();
break;
}
......@@ -2042,7 +2063,7 @@ void AutofillManager::TriggerRefill(const FormData& form,
base::string16 cvc;
FillOrPreviewDataModelForm(
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_refill=*/true);
}
......
......@@ -289,18 +289,15 @@ class AutofillManager : public AutofillHandler,
}
private:
// Keeps track of the filling context for a form, used to make refill attemps.
struct FillingContext {
FillingContext();
~FillingContext();
void Reset();
// Whether a refill attempts was made on that page.
// Whether a refill attempt was made.
bool attempted_refill = false;
// The profile that was used for the initial fill.
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.
base::string16 filled_field_name;
// The time at which the initial fill occured.
......@@ -308,7 +305,7 @@ class AutofillManager : public AutofillHandler,
// The timer used to trigger a refill.
base::OneShotTimer on_refill_timer;
// The field type groups that were initially filled.
std::set<FieldTypeGroup> type_groups_to_refill;
std::set<FieldTypeGroup> type_groups_originally_filled;
};
// AutofillDownloadManager::Observer:
......@@ -592,8 +589,10 @@ class AutofillManager : public AutofillHandler,
AutofillAssistant autofill_assistant_;
#endif
// Filling context used for dynamic fills.
FillingContext filling_context_;
// A map of form names to FillingContext instances used to make refill
// attempts for dynamic forms.
std::map<base::string16, std::unique_ptr<FillingContext>>
filling_contexts_map_;
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