Commit 4c7b65f6 authored by Vadym Doroshenko's avatar Vadym Doroshenko Committed by Commit Bot

Fill username field with prefilled values by server hints.

This CL is implementation of overriding of prefilled values in username
fields if the server believes that these values are placeholders.

The life of server prediction for filling prefilled values is the
following (it's called may_use_prefilled_placeholder in code):
1. The server sends it and it's propagated as part of FormStructure to
FormParser (it's implemented before this CL).
2. It's parsed in password_field_prediction.cc to PasswordFieldPrediction
3. FormParser puts it to PasswordForm.
4. In password_form_fill_data.cc it's propagated to PasswordFormFillData
5. It's sent to the renderer over MOJO
6. PasswordAutofillAgent uses it to override prefilled values if any.

The reason of putting processing may_use_prefilled_placeholder in
FormParser not in NewPasswordFormManager is that NewPasswordFormManager
should know nothing about form structure, it's FormParser responsibility
to process it.

Small deletion of dead code is done: parameter |set_selection| from
FillUserNameAndPassword, since it's never set to true.

Bug: 847793, 831123
Change-Id: Id86d0992428e520718a6ab6e9f50e8064444d030
Reviewed-on: https://chromium-review.googlesource.com/1124474
Commit-Queue: Vadym Doroshenko <dvadym@chromium.org>
Reviewed-by: default avatarIoana Pandele <ioanap@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarVaclav Brozek <vabr@chromium.org>
Cr-Commit-Position: refs/heads/master@{#575597}
parent 6ea2a2e7
......@@ -3604,4 +3604,31 @@ TEST_F(PasswordAutofillAgentTest, FillDataWithNoPasswordId) {
CheckTextFieldsDOMState(kAliceUsername, true, kAlicePassword, true);
}
TEST_F(PasswordAutofillAgentTest, MayUsePlaceholderNoPlaceholder) {
fill_data_.username_may_use_prefilled_placeholder = true;
UpdateRendererIDs();
SimulateOnFillPasswordForm(fill_data_);
CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
}
TEST_F(PasswordAutofillAgentTest, MayUsePlaceholderAndPlaceholderOnForm) {
username_element_.SetValue(WebString::FromUTF8("placeholder"));
UpdateRendererIDs();
fill_data_.username_may_use_prefilled_placeholder = true;
SimulateOnFillPasswordForm(fill_data_);
CheckTextFieldsSuggestedState(kAliceUsername, true, kAlicePassword, true);
}
TEST_F(PasswordAutofillAgentTest, NoMayUsePlaceholderAndPlaceholderOnForm) {
username_element_.SetValue(WebString::FromUTF8("placeholder"));
UpdateRendererIDs();
fill_data_.username_may_use_prefilled_placeholder = false;
SimulateOnFillPasswordForm(fill_data_);
CheckTextFieldsDOMState("placeholder", false, "", false);
}
} // namespace autofill
......@@ -178,6 +178,7 @@ struct PasswordFormFillData {
url.mojom.Url action;
FormFieldData username_field;
FormFieldData password_field;
bool username_may_use_prefilled_placeholder;
string preferred_realm;
map<mojo_base.mojom.String16, PasswordAndRealm> additional_logins;
bool wait_for_username;
......
......@@ -690,6 +690,8 @@ bool StructTraits<autofill::mojom::PasswordFormFillDataDataView,
out->is_possible_change_password_form =
data.is_possible_change_password_form();
out->has_renderer_ids = data.has_renderer_ids();
out->username_may_use_prefilled_placeholder =
data.username_may_use_prefilled_placeholder();
return true;
}
......
......@@ -380,6 +380,11 @@ struct StructTraits<autofill::mojom::PasswordFormFillDataDataView,
return r.password_field;
}
static bool username_may_use_prefilled_placeholder(
const autofill::PasswordFormFillData& r) {
return r.username_may_use_prefilled_placeholder;
}
static const std::string& preferred_realm(
const autofill::PasswordFormFillData& r) {
return r.preferred_realm;
......
......@@ -1726,7 +1726,7 @@ bool PasswordAutofillAgent::FillUserNameAndPassword(
blink::WebInputElement* password_element,
const PasswordFormFillData& fill_data,
bool exact_username_match,
bool set_selection,
bool username_may_use_prefilled_placeholder,
FieldValueAndPropertiesMaskMap* field_value_and_properties_map,
RendererSavePasswordProgressLogger* logger) {
if (logger)
......@@ -1747,23 +1747,28 @@ bool PasswordAutofillAgent::FillUserNameAndPassword(
// not autocompletable (no username case).
base::string16 current_username;
// Whether the username element was prefilled with content that was not on a
// list of known placeholder texts (e.g. "username or email").
bool prefilled_not_placeholder_username = false;
// Whether the username element was prefilled with content that was on a
// list of known placeholder texts that should be overridden (e.g. "username
// or email" or there is a server hint that it is just a placeholder).
bool prefilled_placeholder_username = false;
if (!username_element->IsNull()) {
prefilled_placeholder_username =
!username_element->Value().IsEmpty() &&
(PossiblePrefilledUsernameValue(username_element->Value().Utf8()) ||
username_may_use_prefilled_placeholder);
if (!username_element->Value().IsEmpty() &&
!PossiblePrefilledUsernameValue(username_element->Value().Utf8())) {
!prefilled_placeholder_username) {
// Username is filled with content that was not on a list of known
// placeholder texts (e.g. "username or email").
// placeholder texts (e.g. "username or email") nor there is server-side
// data that this value is placeholder.
current_username = username_element->Value().Utf16();
prefilled_not_placeholder_username = true;
} else if (IsElementAutocompletable(*username_element)) {
current_username = fill_data.username_field.value;
}
}
// username and password will contain the match found if any.
// |username| and |password| will contain the match found if any.
base::string16 username;
base::string16 password;
......@@ -1771,7 +1776,8 @@ bool PasswordAutofillAgent::FillUserNameAndPassword(
logger, &username, &password);
if (password.empty()) {
if (prefilled_not_placeholder_username) {
if (!username_element->IsNull() && !username_element->Value().IsEmpty() &&
!prefilled_placeholder_username) {
LogPrefilledUsernameFillOutcome(
PrefilledUsernameFillOutcome::kPrefilledUsernameNotOverridden);
}
......@@ -1786,34 +1792,24 @@ bool PasswordAutofillAgent::FillUserNameAndPassword(
// Input matches the username, fill in required values.
if (!username_element->IsNull() &&
IsElementAutocompletable(*username_element)) {
// Fill a non-empty username if it is safe to override the value of the
// username element. It is safe to override if the value is empty or a known
// placeholder value.
if (!username.empty()) {
if (username_element->Value().IsEmpty()) {
username_element->SetSuggestedValue(
blink::WebString::FromUTF16(username));
gatekeeper_.RegisterElement(username_element);
} else if (PossiblePrefilledUsernameValue(
username_element->Value().Utf8())) {
if (!username.empty() && (username_element->Value().IsEmpty() ||
prefilled_placeholder_username)) {
username_element->SetSuggestedValue(
blink::WebString::FromUTF16(username));
gatekeeper_.RegisterElement(username_element);
if (prefilled_placeholder_username) {
LogPrefilledUsernameFillOutcome(
PrefilledUsernameFillOutcome::
kPrefilledPlaceholderUsernameOverridden);
}
}
UpdateFieldValueAndPropertiesMaskMap(*username_element, &username,
FieldPropertiesFlags::AUTOFILLED,
field_value_and_properties_map);
username_element->SetAutofillState(WebAutofillState::kAutofilled);
if (logger)
logger->LogElementName(Logger::STRING_USERNAME_FILLED, *username_element);
if (set_selection) {
form_util::PreviewSuggestion(username, current_username,
username_element);
}
}
// Wait to fill in the password until a user gesture occurs. This is to make
......@@ -1875,7 +1871,8 @@ bool PasswordAutofillAgent::FillFormOnPasswordReceived(
// match for read-only username fields.
return FillUserNameAndPassword(
&username_element, &password_element, fill_data, exact_username_match,
false /* set_selection */, field_value_and_properties_map, logger);
fill_data.username_may_use_prefilled_placeholder,
field_value_and_properties_map, logger);
}
void PasswordAutofillAgent::OnProvisionallySaveForm(
......
......@@ -307,12 +307,14 @@ class PasswordAutofillAgent : public content::RenderFrameObserver,
// will only have the suggestedValue set. If a match is found, return true and
// |field_value_and_properties_map| will be modified with the autofilled
// credentials and |FieldPropertiesFlags::AUTOFILLED| flag.
// If |username_may_use_prefilled_placeholder| then this function may
// overwrite the value of username field.
bool FillUserNameAndPassword(
blink::WebInputElement* username_element,
blink::WebInputElement* password_element,
const PasswordFormFillData& fill_data,
bool exact_username_match,
bool set_selection,
bool username_may_use_prefilled_placeholder,
FieldValueAndPropertiesMaskMap* field_value_and_properties_map,
RendererSavePasswordProgressLogger* logger);
......
......@@ -177,6 +177,11 @@ struct PasswordForm {
uint32_t username_element_renderer_id =
FormFieldData::kNotSetFormControlRendererId;
// True if the server-side classification believes that the field may be
// pre-filled with a placeholder in the value attribute. It is set during
// form parsing and not persisted.
bool username_may_use_prefilled_placeholder = false;
// Whether the |username_element| has an autocomplete=username attribute. This
// is only used in parsed HTML forms.
bool username_marked_by_site;
......
......@@ -42,6 +42,8 @@ void InitPasswordFormFillData(
username_field.name = form_on_page.username_element;
username_field.value = preferred_match->username_value;
username_field.unique_renderer_id = form_on_page.username_element_renderer_id;
result->username_may_use_prefilled_placeholder =
form_on_page.username_may_use_prefilled_placeholder;
FormFieldData password_field;
password_field.name = form_on_page.password_element;
password_field.value = preferred_match->password_value;
......
......@@ -64,6 +64,10 @@ struct PasswordFormFillData {
FormFieldData username_field;
FormFieldData password_field;
// True if the server-side classification believes that the field may be
// pre-filled with a placeholder in the value attribute.
bool username_may_use_prefilled_placeholder;
// The signon realm of the preferred user/pass pair.
std::string preferred_realm;
......
......@@ -249,6 +249,7 @@ TEST(PasswordFormFillDataTest, RendererIDs) {
form_on_page.action = GURL("https://foo.com/login");
form_on_page.username_element = ASCIIToUTF16("username");
form_on_page.password_element = ASCIIToUTF16("password");
form_on_page.username_may_use_prefilled_placeholder = true;
// Create an exact match in the database.
PasswordForm preferred_match = form_on_page;
......@@ -277,6 +278,7 @@ TEST(PasswordFormFillDataTest, RendererIDs) {
result.username_field.unique_renderer_id);
EXPECT_EQ(form_on_page.password_element_renderer_id,
result.password_field.unique_renderer_id);
EXPECT_TRUE(result.username_may_use_prefilled_placeholder);
}
} // namespace autofill
......@@ -576,15 +576,33 @@ const FormFieldData* FindUsernameInPredictions(
return nullptr;
}
// Return true if |significant_fields| has an username field and
// |form_predictions| has |may_use_prefilled_placeholder| == true for the
// username field.
bool GetMayUsePrefilledPlaceholder(
const FormPredictions* form_predictions,
const SignificantFields& significant_fields) {
if (!form_predictions || !significant_fields.username)
return false;
uint32_t username_id = significant_fields.username->unique_renderer_id;
auto it = form_predictions->find(username_id);
if (it == form_predictions->end())
return false;
return it->second.may_use_prefilled_placeholder;
}
// Puts together a PasswordForm, the result of the parsing, based on the
// |form_data| description of the form metadata (e.g., action), the already
// parsed information about what are the |significant_fields|, and the list
// |all_possible_passwords| of all non-empty password values and associated
// element names which occurred in the form.
// element names which occurred in the form. |form_predictions| is used to find
// fields that may have preffilled placeholders.
std::unique_ptr<PasswordForm> AssemblePasswordForm(
const autofill::FormData& form_data,
const SignificantFields* significant_fields,
autofill::ValueElementVector all_possible_passwords) {
autofill::ValueElementVector all_possible_passwords,
const FormPredictions* form_predictions) {
if (!significant_fields || !significant_fields->HasPasswords())
return nullptr;
......@@ -599,6 +617,8 @@ std::unique_ptr<PasswordForm> AssemblePasswordForm(
result->preferred = false;
result->blacklisted_by_user = false;
result->type = PasswordForm::TYPE_MANUAL;
result->username_may_use_prefilled_placeholder =
GetMayUsePrefilledPlaceholder(form_predictions, *significant_fields);
// Set data related to specific fields.
SetFields(*significant_fields, result.get());
......@@ -650,7 +670,8 @@ std::unique_ptr<PasswordForm> ParseFormData(
ParseUsingBaseHeuristics(processed_fields, mode, significant_fields.get());
return AssemblePasswordForm(form_data, significant_fields.get(),
std::move(all_possible_passwords));
std::move(all_possible_passwords),
form_predictions);
}
} // namespace password_manager
......@@ -79,6 +79,7 @@ struct FormParsingTestCase {
int number_of_all_possible_passwords = -1;
// null means no checking
const autofill::ValueElementVector* all_possible_passwords = nullptr;
bool username_may_use_prefilled_placeholder = false;
};
// Returns numbers which are distinct from each other within the scope of one
......@@ -311,6 +312,8 @@ void CheckTestData(const std::vector<FormParsingTestCase>& test_cases) {
EXPECT_FALSE(parsed_form->blacklisted_by_user);
EXPECT_EQ(PasswordForm::TYPE_MANUAL, parsed_form->type);
EXPECT_TRUE(parsed_form->has_renderer_ids);
EXPECT_EQ(test_case.username_may_use_prefilled_placeholder,
parsed_form->username_may_use_prefilled_placeholder);
CheckPasswordFormFields(*parsed_form, form_data, expected_ids);
CheckAllValuesUnique(parsed_form->all_possible_passwords);
if (test_case.number_of_all_possible_passwords >= 0) {
......@@ -918,7 +921,8 @@ TEST(FormParserTest, ServerHints) {
"Username-only predictions are ignored",
{
{.form_control_type = "text",
.prediction = {.type = autofill::USERNAME}},
.prediction = {.type = autofill::USERNAME,
.may_use_prefilled_placeholder = true}},
{.role = ElementRole::USERNAME, .form_control_type = "text"},
{.role = ElementRole::CURRENT_PASSWORD,
.form_control_type = "password"},
......@@ -929,13 +933,16 @@ TEST(FormParserTest, ServerHints) {
{
{.role = ElementRole::USERNAME,
.form_control_type = "text",
.prediction = {.type = autofill::USERNAME_AND_EMAIL_ADDRESS}},
.prediction = {.type = autofill::USERNAME_AND_EMAIL_ADDRESS,
.may_use_prefilled_placeholder = true}},
{.form_control_type = "text"},
{.form_control_type = "password"},
{.role = ElementRole::CURRENT_PASSWORD,
.prediction = {.type = autofill::PASSWORD},
.prediction = {.type = autofill::PASSWORD,
.may_use_prefilled_placeholder = true},
.form_control_type = "password"},
},
.username_may_use_prefilled_placeholder = true,
},
{
.description_for_logging = "Longer predictions work",
......
......@@ -42,12 +42,19 @@ CredentialFieldType DeriveFromServerFieldType(ServerFieldType type) {
FormPredictions ConvertToFormPredictions(const FormStructure& form_structure) {
FormPredictions result;
for (const auto& field : form_structure) {
ServerFieldType server_type = field->server_type();
if (IsCredentialRelatedPrediction(server_type))
result[field->unique_renderer_id] =
PasswordFieldPrediction{.type = server_type};
if (IsCredentialRelatedPrediction(server_type)) {
bool may_use_prefilled_placeholder = false;
for (const auto& predictions : field->server_predictions()) {
may_use_prefilled_placeholder |=
predictions.may_use_prefilled_placeholder();
}
result[field->unique_renderer_id] = PasswordFieldPrediction{
.type = server_type,
.may_use_prefilled_placeholder = may_use_prefilled_placeholder};
}
}
return result;
......
......@@ -29,12 +29,9 @@ enum class CredentialFieldType {
CredentialFieldType DeriveFromServerFieldType(autofill::ServerFieldType type);
// Contains server predictions for a field.
// This is the struct rather than using because it will be expanded soon with
// additional information.
// TODO(https://crbug.com/831123): Remove comment about struct usage purposes as
// soon as additional fields added.
struct PasswordFieldPrediction {
autofill::ServerFieldType type;
bool may_use_prefilled_placeholder = false;
};
// Contains server predictions for a form. Keys are unique renderer ids of
......
......@@ -27,6 +27,9 @@ using autofill::USERNAME;
using autofill::USERNAME_AND_EMAIL_ADDRESS;
using base::ASCIIToUTF16;
using FieldPrediction =
autofill::AutofillQueryResponseContents::Field::FieldPrediction;
namespace password_manager {
namespace {
......@@ -37,14 +40,15 @@ TEST(FormPredictionsTest, ConvertToFormPredictions) {
std::string form_control_type;
ServerFieldType input_type;
ServerFieldType expected_type;
bool may_use_prefilled_placeholder;
} test_fields[] = {
{"full_name", "text", UNKNOWN_TYPE, UNKNOWN_TYPE},
{"full_name", "text", UNKNOWN_TYPE, UNKNOWN_TYPE, false},
// Password Manager is interested only in credential related types.
{"Email", "email", EMAIL_ADDRESS, UNKNOWN_TYPE},
{"username", "text", USERNAME, USERNAME},
{"Password", "password", PASSWORD, PASSWORD},
{"Email", "email", EMAIL_ADDRESS, UNKNOWN_TYPE, false},
{"username", "text", USERNAME, USERNAME, true},
{"Password", "password", PASSWORD, PASSWORD, false},
{"confirm_password", "password", CONFIRMATION_PASSWORD,
CONFIRMATION_PASSWORD}};
CONFIRMATION_PASSWORD, true}};
FormData form_data;
for (size_t i = 0; i < base::size(test_fields); ++i) {
......@@ -57,11 +61,17 @@ TEST(FormPredictionsTest, ConvertToFormPredictions) {
FormStructure form_structure(form_data);
size_t expected_predictions = 0;
// Set server predictions and create expectected votes.
// Set server predictions and create expected votes.
for (size_t i = 0; i < base::size(test_fields); ++i) {
AutofillField* field = form_structure.field(i);
field->set_server_type(test_fields[i].input_type);
ServerFieldType expected_type = test_fields[i].expected_type;
FieldPrediction prediction;
prediction.set_may_use_prefilled_placeholder(
test_fields[i].may_use_prefilled_placeholder);
field->set_server_predictions({prediction});
if (expected_type != UNKNOWN_TYPE)
++expected_predictions;
}
......@@ -79,6 +89,8 @@ TEST(FormPredictionsTest, ConvertToFormPredictions) {
} else {
ASSERT_NE(actual_predictions.end(), it);
EXPECT_EQ(test_fields[i].expected_type, it->second.type);
EXPECT_EQ(test_fields[i].may_use_prefilled_placeholder,
it->second.may_use_prefilled_placeholder);
}
}
}
......
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