Commit 9baf76b3 authored by Maria Kazinova's avatar Maria Kazinova Committed by Commit Bot

[iOS] Using numeric renderer IDs for Autofill form filling.

Bug: 1075444, 1131038
Change-Id: I276aa7628088d810e422235f85564f0ac0930681
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2431428Reviewed-by: default avatarVadym Doroshenko  <dvadym@chromium.org>
Reviewed-by: default avatarOlivier Robin <olivierrobin@chromium.org>
Commit-Queue: Maria Kazinova <kazinova@google.com>
Cr-Commit-Position: refs/heads/master@{#811789}
parent acda9626
......@@ -16,6 +16,7 @@
#include "base/memory/weak_ptr.h"
#include "base/metrics/field_trial.h"
#include "base/strings/string16.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
......@@ -62,8 +63,10 @@
#error "This file requires ARC support."
#endif
using base::NumberToString;
using base::SysNSStringToUTF8;
using base::SysNSStringToUTF16;
using base::SysUTF16ToNSString;
using autofill::FormRendererId;
using autofill::FieldDataManager;
using autofill::FieldRendererId;
......@@ -145,10 +148,11 @@ void UpdateFieldManagerForClearedIDs(
// Manager for Autofill JavaScripts.
JsAutofillManager* _jsAutofillManager;
// The name of the most recent autocomplete field; tracks the currently-
// focused form element in order to force filling of the currently selected
// form element, even if it's non-empty.
// The name and the unique renderer ID of the most recent autocomplete field;
// tracks the currently-focused form element in order to force filling of
// the currently selected form element, even if it's non-empty.
base::string16 _pendingAutocompleteField;
FieldRendererId _pendingAutocompleteFieldID;
// Suggestions state:
// The most recent form suggestions.
......@@ -428,6 +432,7 @@ autofillManagerFromWebState:(web::WebState*)webState
if (suggestion.identifier > 0) {
_pendingAutocompleteField = SysNSStringToUTF16(fieldIdentifier);
_pendingAutocompleteFieldID = uniqueFieldID;
if (_popupDelegate) {
// TODO(966411): Replace 0 with the index of the selected suggestion.
_popupDelegate->DidAcceptSuggestion(SysNSStringToUTF16(suggestion.value),
......@@ -482,7 +487,14 @@ autofillManagerFromWebState:(web::WebState*)webState
auto autofillData =
std::make_unique<base::Value>(base::Value::Type::DICTIONARY);
autofillData->SetKey("formName", base::Value(base::UTF16ToUTF8(form.name)));
uint32_t formRendererID = form.unique_renderer_id
? form.unique_renderer_id.value()
: autofill::kNotSetRendererID;
autofillData->SetKey("formRendererID",
base::Value(static_cast<int>(formRendererID)));
bool useRendererIDs = base::FeatureList::IsEnabled(
autofill::features::kAutofillUseUniqueRendererIDsOnIOS);
base::Value fieldsData(base::Value::Type::DICTIONARY);
for (const auto& field : form.fields) {
// Skip empty fields and those that are not autofilled.
......@@ -492,7 +504,15 @@ autofillManagerFromWebState:(web::WebState*)webState
base::Value fieldData(base::Value::Type::DICTIONARY);
fieldData.SetKey("value", base::Value(field.value));
fieldData.SetKey("section", base::Value(field.section));
fieldsData.SetKey(base::UTF16ToUTF8(field.unique_id), std::move(fieldData));
uint32_t fieldRendererID = field.unique_renderer_id
? field.unique_renderer_id.value()
: autofill::kNotSetRendererID;
if (useRendererIDs) {
fieldsData.SetKey(NumberToString(fieldRendererID), std::move(fieldData));
} else {
fieldsData.SetKey(base::UTF16ToUTF8(field.unique_id),
std::move(fieldData));
}
}
autofillData->SetKey("fields", std::move(fieldsData));
......@@ -570,16 +590,16 @@ autofillManagerFromWebState:(web::WebState*)webState
// Value will contain the text to be filled in the selected element while
// displayDescription will contain a summary of the data to be filled in
// the other elements.
value = base::SysUTF16ToNSString(popup_suggestion.value);
displayDescription = base::SysUTF16ToNSString(popup_suggestion.label);
value = SysUTF16ToNSString(popup_suggestion.value);
displayDescription = SysUTF16ToNSString(popup_suggestion.label);
} else if (popup_suggestion.frontend_id ==
autofill::POPUP_ITEM_ID_CLEAR_FORM) {
// Show the "clear form" button.
value = base::SysUTF16ToNSString(popup_suggestion.value);
value = SysUTF16ToNSString(popup_suggestion.value);
} else if (popup_suggestion.frontend_id ==
autofill::POPUP_ITEM_ID_SHOW_ACCOUNT_CARDS) {
// Show opt-in for showing cards from account.
value = base::SysUTF16ToNSString(popup_suggestion.value);
value = SysUTF16ToNSString(popup_suggestion.value);
}
if (!value)
......@@ -917,8 +937,8 @@ autofillManagerFromWebState:(web::WebState*)webState
} copy];
__weak AutofillAgent* weakSelf = self;
[_jsAutofillManager fillForm:std::move(data)
forceFillFieldIdentifier:base::SysUTF16ToNSString(
_pendingAutocompleteField)
forceFillFieldIdentifier:SysUTF16ToNSString(_pendingAutocompleteField)
forceFillFieldUniqueID:_pendingAutocompleteFieldID
inFrame:frame
completionHandler:^(NSString* jsonString) {
AutofillAgent* strongSelf = weakSelf;
......
......@@ -125,7 +125,14 @@ class AutofillAgentTests : public PlatformTest {
// Tests that form's name and fields' identifiers, values, and whether they are
// autofilled are sent to the JS. Fields with empty values and those that are
// not autofilled are skipped.
// TODO(crbug/1131038): Remove once using only renderer IDs is launched.
TEST_F(AutofillAgentTests, OnFormDataFilledTestWithFrameMessaging) {
base::test::ScopedFeatureList scoped_feature_list;
std::vector<base::Feature> disabled_features;
disabled_features.push_back(
autofill::features::kAutofillUseUniqueRendererIDsOnIOS);
scoped_feature_list.InitWithFeatures({}, disabled_features);
std::string locale("en");
autofill::AutofillDriverIOS::PrepareForWebStateWebFrameAndDelegate(
&test_web_state_, &client_, nil, locale,
......@@ -135,6 +142,7 @@ TEST_F(AutofillAgentTests, OnFormDataFilledTestWithFrameMessaging) {
form.url = GURL("https://myform.com");
form.action = GURL("https://myform.com/submit");
form.name = base::ASCIIToUTF16("CC form");
form.unique_renderer_id = FormRendererId(0);
autofill::FormFieldData field;
field.form_control_type = "text";
......@@ -145,6 +153,7 @@ TEST_F(AutofillAgentTests, OnFormDataFilledTestWithFrameMessaging) {
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("number_value");
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(1);
form.fields.push_back(field);
field.label = base::ASCIIToUTF16("Name on Card");
field.name = base::ASCIIToUTF16("name");
......@@ -153,6 +162,7 @@ TEST_F(AutofillAgentTests, OnFormDataFilledTestWithFrameMessaging) {
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("name_value");
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(2);
form.fields.push_back(field);
field.label = base::ASCIIToUTF16("Expiry Month");
field.name = base::ASCIIToUTF16("expiry_month");
......@@ -161,6 +171,7 @@ TEST_F(AutofillAgentTests, OnFormDataFilledTestWithFrameMessaging) {
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("01");
field.is_autofilled = false;
field.unique_renderer_id = FieldRendererId(3);
form.fields.push_back(field);
field.label = base::ASCIIToUTF16("Unknown field");
field.name = base::ASCIIToUTF16("unknown");
......@@ -169,6 +180,7 @@ TEST_F(AutofillAgentTests, OnFormDataFilledTestWithFrameMessaging) {
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("");
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(4);
form.fields.push_back(field);
[autofill_agent_
fillFormData:form
......@@ -178,14 +190,92 @@ TEST_F(AutofillAgentTests, OnFormDataFilledTestWithFrameMessaging) {
"__gCrWeb.autofill.fillForm({\"fields\":{\"name\":{\"section\":\"\","
"\"value\":\"name_value\"},"
"\"number\":{\"section\":\"\",\"value\":\"number_value\"}},"
"\"formName\":\"CC form\"}, \"\");",
"\"formName\":\"CC form\",\"formRendererID\":0}, \"\", -1, false);",
fake_main_frame_->GetLastJavaScriptCall());
}
// Tests that form's name and fields' identifiers, values, and whether they are
// autofilled are sent to the JS. Fields with empty values and those that are
// not autofilled are skipped. Tests logic based on renderer ids usage.
TEST_F(AutofillAgentTests,
OnFormDataFilledTestWithFrameMessagingUsingRendererIDs) {
base::test::ScopedFeatureList scoped_feature_list;
std::vector<base::Feature> enabled_features;
enabled_features.push_back(
autofill::features::kAutofillUseUniqueRendererIDsOnIOS);
scoped_feature_list.InitWithFeatures(enabled_features, {});
std::string locale("en");
autofill::AutofillDriverIOS::PrepareForWebStateWebFrameAndDelegate(
&test_web_state_, &client_, nil, locale,
autofill::AutofillManager::DISABLE_AUTOFILL_DOWNLOAD_MANAGER);
autofill::FormData form;
form.url = GURL("https://myform.com");
form.action = GURL("https://myform.com/submit");
form.name = base::ASCIIToUTF16("CC form");
form.unique_renderer_id = FormRendererId(0);
autofill::FormFieldData field;
field.form_control_type = "text";
field.label = base::ASCIIToUTF16("Card number");
field.name = base::ASCIIToUTF16("number");
field.name_attribute = field.name;
field.id_attribute = base::ASCIIToUTF16("number");
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("number_value");
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(1);
form.fields.push_back(field);
field.label = base::ASCIIToUTF16("Name on Card");
field.name = base::ASCIIToUTF16("name");
field.name_attribute = field.name;
field.id_attribute = base::ASCIIToUTF16("name");
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("name_value");
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(2);
form.fields.push_back(field);
field.label = base::ASCIIToUTF16("Expiry Month");
field.name = base::ASCIIToUTF16("expiry_month");
field.name_attribute = field.name;
field.id_attribute = base::ASCIIToUTF16("expiry_month");
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("01");
field.is_autofilled = false;
field.unique_renderer_id = FieldRendererId(3);
form.fields.push_back(field);
field.label = base::ASCIIToUTF16("Unknown field");
field.name = base::ASCIIToUTF16("unknown");
field.name_attribute = field.name;
field.id_attribute = base::ASCIIToUTF16("unknown");
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("");
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(4);
form.fields.push_back(field);
[autofill_agent_
fillFormData:form
inFrame:test_web_state_.GetWebFramesManager()->GetMainWebFrame()];
test_web_state_.WasShown();
EXPECT_EQ("__gCrWeb.autofill.fillForm({\"fields\":{\"1\":{\"section\":\"\","
"\"value\":\"number_value\"},"
"\"2\":{\"section\":\"\",\"value\":\"name_value\"}},"
"\"formName\":\"CC form\",\"formRendererID\":0}, \"\", -1, true);",
fake_main_frame_->GetLastJavaScriptCall());
}
// Tests that in the case of conflict in fields' identifiers, the last seen
// value of a given field is used.
// TODO(crbug/1131038): Remove once using only renderer IDs is launched.
TEST_F(AutofillAgentTests,
OnFormDataFilledWithNameCollisionTestFrameMessaging) {
base::test::ScopedFeatureList scoped_feature_list;
std::vector<base::Feature> disabled_features;
disabled_features.push_back(
autofill::features::kAutofillUseUniqueRendererIDsOnIOS);
scoped_feature_list.InitWithFeatures({}, disabled_features);
std::string locale("en");
autofill::AutofillDriverIOS::PrepareForWebStateWebFrameAndDelegate(
&test_web_state_, &client_, nil, locale,
......@@ -194,6 +284,7 @@ TEST_F(AutofillAgentTests,
autofill::FormData form;
form.url = GURL("https://myform.com");
form.action = GURL("https://myform.com/submit");
form.unique_renderer_id = FormRendererId(0);
autofill::FormFieldData field;
field.form_control_type = "text";
......@@ -204,6 +295,7 @@ TEST_F(AutofillAgentTests,
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("California");
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(1);
form.fields.push_back(field);
field.label = base::ASCIIToUTF16("Other field");
field.name = base::ASCIIToUTF16("field1");
......@@ -212,6 +304,7 @@ TEST_F(AutofillAgentTests,
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("value 1");
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(2);
form.fields.push_back(field);
field.label = base::ASCIIToUTF16("Other field");
field.name = base::ASCIIToUTF16("field1");
......@@ -220,6 +313,7 @@ TEST_F(AutofillAgentTests,
field.unique_id = field.id_attribute;
field.value = base::ASCIIToUTF16("value 2");
field.is_autofilled = true;
field.unique_renderer_id = FieldRendererId(3);
form.fields.push_back(field);
// Fields are in alphabetical order.
[autofill_agent_
......@@ -229,7 +323,7 @@ TEST_F(AutofillAgentTests,
EXPECT_EQ("__gCrWeb.autofill.fillForm({\"fields\":{\"field1\":{\"section\":"
"\"\",\"value\":\"value "
"2\"},\"region\":{\"section\":\"\",\"value\":\"California\"}},"
"\"formName\":\"\"}, \"\");",
"\"formName\":\"\",\"formRendererID\":0}, \"\", -1, false);",
fake_main_frame_->GetLastJavaScriptCall());
}
......
......@@ -8,6 +8,7 @@
#include "base/ios/block_types.h"
#include "base/values.h"
#include "components/autofill/core/common/autofill_constants.h"
#include "components/autofill/core/common/renderer_id.h"
namespace web {
class WebFrame;
......@@ -43,6 +44,7 @@ class WebFrame;
// corresponding filled values. |completionHandler| cannot be nil.
- (void)fillForm:(std::unique_ptr<base::Value>)data
forceFillFieldIdentifier:(NSString*)forceFillFieldIdentifier
forceFillFieldUniqueID:(autofill::FieldRendererId)forceFillFieldUniqueID
inFrame:(web::WebFrame*)frame
completionHandler:(void (^)(NSString*))completionHandler;
......
......@@ -24,6 +24,8 @@
#error "This file requires ARC support."
#endif
using autofill::FieldRendererId;
@implementation JsAutofillManager
- (void)addJSDelayInFrame:(web::WebFrame*)frame {
......@@ -93,17 +95,26 @@
- (void)fillForm:(std::unique_ptr<base::Value>)data
forceFillFieldIdentifier:(NSString*)forceFillFieldIdentifier
forceFillFieldUniqueID:(FieldRendererId)forceFillFieldUniqueID
inFrame:(web::WebFrame*)frame
completionHandler:(void (^)(NSString*))completionHandler {
DCHECK(data);
DCHECK(completionHandler);
std::string fieldIdentifier =
bool useRendererIDs = base::FeatureList::IsEnabled(
autofill::features::kAutofillUseUniqueRendererIDsOnIOS);
std::string fieldStringID =
forceFillFieldIdentifier
? base::SysNSStringToUTF8(forceFillFieldIdentifier)
: "null";
int fieldNumericID = forceFillFieldUniqueID ? forceFillFieldUniqueID.value()
: autofill::kNotSetRendererID;
std::vector<base::Value> parameters;
parameters.push_back(std::move(*data));
parameters.push_back(base::Value(fieldIdentifier));
parameters.push_back(base::Value(fieldStringID));
parameters.push_back(base::Value(fieldNumericID));
parameters.push_back(base::Value(useRendererIDs));
autofill::ExecuteJavaScriptFunction(
"autofill.fillForm", parameters, frame,
autofill::CreateStringCallback(completionHandler));
......
......@@ -21,6 +21,7 @@ goog.provide('__crWeb.autofill');
* The autofill data for a form.
* @typedef {{
* formName: string,
* formRendererID: number,
* fields: !Object<string, !Object<string, string>>,
* }}
*/
......@@ -189,11 +190,16 @@ function controlElementInputListener_(evt) {
* |forceFillFieldName| will always be filled even if non-empty.
*
* @param {!FormData} data Autofill data to fill in.
* @param {string} forceFillFieldIdentifier Identified field will always be
* @param {string} forceFillFieldStringID Identified field will always be
* filled even if non-empty. May be null.
* @param {number} forceFillFieldNumericID Identified field will always be
* filled even if non-empty. May be kNotSetRendererId.
* @param {bool} useRendererIDs Whether the logic should use numeric renderer
* IDs for form filling.
* @return {string} JSON encoded list of renderer IDs of filled elements.
*/
__gCrWeb.autofill['fillForm'] = function(data, forceFillFieldIdentifier) {
__gCrWeb.autofill['fillForm'] = function(
data, forceFillFieldStringID, forceFillFieldNumericID, useRendererIDs) {
// Inject CSS to style the autofilled elements with a yellow background.
if (!__gCrWeb.autofill.styleInjected) {
const style = document.createElement('style');
......@@ -207,7 +213,10 @@ __gCrWeb.autofill['fillForm'] = function(data, forceFillFieldIdentifier) {
}
const filledElements = {};
const form = __gCrWeb.form.getFormElementFromIdentifier(data.formName);
const form = useRendererIDs ?
__gCrWeb.form.getFormElementFromUniqueFormId(data.formRendererID) :
__gCrWeb.form.getFormElementFromIdentifier(data.formName);
const controlElements = form ?
__gCrWeb.form.getFormControlElements(form) :
__gCrWeb.fill.getUnownedAutofillableFormFieldElements(
......@@ -227,7 +236,9 @@ __gCrWeb.autofill['fillForm'] = function(data, forceFillFieldIdentifier) {
// Skip fields for which autofill data is missing.
const fieldIdentifier = __gCrWeb.form.getFieldIdentifier(element);
const fieldData = data.fields[fieldIdentifier];
const fieldRendererID = __gCrWeb.fill.getUniqueID(element);
const fieldData = useRendererIDs ? data.fields[fieldRendererID] :
data.fields[fieldIdentifier];
if (!fieldData) {
continue;
}
......@@ -238,10 +249,12 @@ __gCrWeb.autofill['fillForm'] = function(data, forceFillFieldIdentifier) {
// always autofilled; see AutofillManager::FillOrPreviewDataModelForm().
// c) The "value" or "placeholder" attributes match the value, if any; or
// d) The value has not been set by the user.
const shouldBeForceFilled = useRendererIDs ?
fieldRendererID === forceFillFieldNumericID :
fieldIdentifier === forceFillFieldStringID;
if (element.value && __gCrWeb.form.fieldWasEditedByUser(element) &&
!__gCrWeb.autofill.sanitizedFieldIsEmpty(element.value) &&
fieldIdentifier !== forceFillFieldIdentifier &&
!__gCrWeb.fill.isSelectElement(element) &&
!shouldBeForceFilled && !__gCrWeb.fill.isSelectElement(element) &&
!((element.hasAttribute('value') &&
element.getAttribute('value') === element.value) ||
(element.hasAttribute('placeholder') &&
......
......@@ -24,6 +24,7 @@
#error "This file requires ARC support."
#endif
using autofill::FieldRendererId;
using base::SysNSStringToUTF8;
namespace {
......@@ -507,9 +508,16 @@ TEST_F(JsAutofillManagerTest, TestExtractedFieldsIDs) {
}
}
// Tests form filling (fillForm:forceFillIdentifier:inFrame:completionHandler:)
// method.
// Tests form filling (fillForm:forceFillFieldIdentifier:forceFillFieldUniqueID:
// :inFrame:completionHandler:) method.
// TODO(crbug/1131038): Remove once using only renderer IDs is launched.
TEST_F(JsAutofillManagerTest, FillForm) {
base::test::ScopedFeatureList scoped_feature_list;
std::vector<base::Feature> disabled_features;
disabled_features.push_back(
autofill::features::kAutofillUseUniqueRendererIDsOnIOS);
scoped_feature_list.InitWithFeatures({}, disabled_features);
LoadHtml(@"<html><body><form name='testform' method='post'>"
"<input type='text' id='firstname' name='firstname'/>"
"<input type='email' id='email' name='email'/>"
......@@ -538,6 +546,58 @@ TEST_F(JsAutofillManagerTest, FillForm) {
__block BOOL block_was_called = NO;
[manager_ fillForm:std::move(autofillData)
forceFillFieldIdentifier:@"firstname"
forceFillFieldUniqueID:FieldRendererId(1)
inFrame:main_web_frame()
completionHandler:^(NSString* result) {
filling_result = [result copy];
block_was_called = YES;
}];
EXPECT_TRUE(base::test::ios::WaitUntilConditionOrTimeout(
base::test::ios::kWaitForActionTimeout, ^bool() {
return block_was_called;
}));
EXPECT_NSEQ(@"{\"1\":\"Cool User\",\"2\":\"coolemail@com\"}", filling_result);
}
// Tests form filling (fillForm:forceFillFieldIdentifier:forceFillFieldUniqueID:
// :inFrame:completionHandler:) method.
TEST_F(JsAutofillManagerTest, FillFormUsingRendererIDs) {
base::test::ScopedFeatureList scoped_feature_list;
std::vector<base::Feature> enabled_features;
enabled_features.push_back(
autofill::features::kAutofillUseUniqueRendererIDsOnIOS);
scoped_feature_list.InitWithFeatures(enabled_features, {});
LoadHtml(@"<html><body><form name='testform' method='post'>"
"<input type='text' id='firstname' name='firstname'/>"
"<input type='email' id='email' name='email'/>"
"</form></body></html>");
RunFormsSearch();
auto autofillData = std::make_unique<base::DictionaryValue>();
autofillData->SetKey("formName", base::Value("testform"));
autofillData->SetKey("formRendererID", base::Value(0));
base::Value fieldsData(base::Value::Type::DICTIONARY);
base::Value firstFieldData(base::Value::Type::DICTIONARY);
firstFieldData.SetStringKey("name", "firstname");
firstFieldData.SetStringKey("identifier", "firstname");
firstFieldData.SetStringKey("value", "Cool User");
fieldsData.SetKey("1", std::move(firstFieldData));
base::Value secondFieldData(base::Value::Type::DICTIONARY);
secondFieldData.SetStringKey("name", "email");
secondFieldData.SetStringKey("identifier", "email");
secondFieldData.SetStringKey("value", "coolemail@com");
fieldsData.SetKey("2", std::move(secondFieldData));
autofillData->SetKey("fields", std::move(fieldsData));
__block NSString* filling_result = nil;
__block BOOL block_was_called = NO;
[manager_ fillForm:std::move(autofillData)
forceFillFieldIdentifier:@"firstname"
forceFillFieldUniqueID:FieldRendererId(1)
inFrame:main_web_frame()
completionHandler:^(NSString* result) {
filling_result = [result copy];
......
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