Skip to content

Commit

Permalink
[Passwords, FPF] Propagate server predictions to FieldInfoManager
Browse files Browse the repository at this point in the history
This CL propagates form and field signatures, and predicted server field
type to FieldInfoManager. If predictions are already available at the
moment of user typing, they are stored straight away, at the moment
of field info caching.
If predictions arrive later, they are propagated later.

Bug: 1468297
Change-Id: I4a7240cbe2a578c131e40d8f13f81fb27058838d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4770760
Reviewed-by: Maxim Kolosovskiy <kolos@chromium.org>
Commit-Queue: Maria Kazinova <kazinova@google.com>
Cr-Commit-Position: refs/heads/main@{#1184770}
  • Loading branch information
Maria Kazinova authored and Chromium LUCI CQ committed Aug 17, 2023
1 parent ca4759b commit 32ef22b
Show file tree
Hide file tree
Showing 7 changed files with 253 additions and 43 deletions.
49 changes: 48 additions & 1 deletion components/password_manager/core/browser/field_info_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,23 @@ bool IsSameField(const FieldInfo& lhs, const FieldInfo& rhs) {
return lhs.driver_id == rhs.driver_id && lhs.field_id == rhs.field_id;
}

// If |predictions| contain the field identified by |field_info|, stores them in
// |field_info| and returns true.
bool StoresPredictionsForInfo(const FormPredictions& predictions,
FieldInfo& field_info) {
FieldRendererId field_id = field_info.field_id;
auto field = base::ranges::find_if(
predictions.fields, [field_id](const PasswordFieldPrediction& field) {
return field.renderer_id == field_id;
});
if (field == predictions.fields.end()) {
return false;
}
field_info.stored_predictions = predictions;
field_info.type = field->type;
return true;
}

} // namespace

FieldInfo::FieldInfo(int driver_id,
Expand All @@ -31,13 +48,17 @@ FieldInfo::FieldInfo(int driver_id,
FieldInfo::FieldInfo(const FieldInfo&) = default;
FieldInfo& FieldInfo::operator=(const FieldInfo&) = default;

FieldInfo::~FieldInfo() = default;

FieldInfoManager::FieldInfoManager(
scoped_refptr<base::SingleThreadTaskRunner> task_runner)
: task_runner_(task_runner) {}

FieldInfoManager::~FieldInfoManager() = default;

void FieldInfoManager::AddFieldInfo(const FieldInfo& new_info) {
void FieldInfoManager::AddFieldInfo(
const FieldInfo& new_info,
const absl::optional<FormPredictions>& predictions) {
if (!field_info_cache_.empty() &&
IsSameField(field_info_cache_.back().field_info, new_info)) {
// The method can be called on every keystroke while the user modifies
Expand All @@ -60,6 +81,11 @@ void FieldInfoManager::AddFieldInfo(const FieldInfo& new_info) {
field_info_cache_.back().timer->Start(
FROM_HERE, kFieldInfoLifetime, this,
&FieldInfoManager::ClearOldestFieldInfoEntry);

FieldInfo& field_info = field_info_cache_.back().field_info;
if (predictions.has_value() && !field_info.stored_predictions.has_value()) {
StoresPredictionsForInfo(predictions.value(), field_info);
}
}

std::vector<FieldInfo> FieldInfoManager::GetFieldInfo(
Expand All @@ -74,6 +100,27 @@ std::vector<FieldInfo> FieldInfoManager::GetFieldInfo(
return relevant_info;
}

void FieldInfoManager::ProcessServerPredictions(
const std::map<autofill::FormSignature, FormPredictions>& predictions) {
for (auto& entry : field_info_cache_) {
FieldInfo& field_info = entry.field_info;
// Do nothing if predictions are already stored.
if (field_info.stored_predictions.has_value()) {
continue;
}

for (const auto& prediction : predictions) {
// Do nothing if drivers do not match.
if (field_info.driver_id != prediction.second.driver_id) {
continue;
}
if (StoresPredictionsForInfo(prediction.second, field_info)) {
break;
}
}
}
}

FieldInfoManager::FieldInfoEntry::FieldInfoEntry(
FieldInfo field_info,
std::unique_ptr<base::OneShotTimer> timer)
Expand Down
17 changes: 12 additions & 5 deletions components/password_manager/core/browser/field_info_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@
#include "components/autofill/core/common/signatures.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/keyed_service/core/keyed_service.h"
#include "components/password_manager/core/browser/form_parsing/password_field_prediction.h"

namespace password_manager {

struct FormPredictions;

constexpr base::TimeDelta kFieldInfoLifetime = base::Minutes(5);

struct FieldInfo {
Expand All @@ -38,21 +41,20 @@ struct FieldInfo {
// The type of the field predicted by the server.
autofill::ServerFieldType type = autofill::ServerFieldType::UNKNOWN_TYPE;

// Signatures identifying the form and field on the server.
autofill::FormSignature form_signature;
autofill::FieldSignature field_signature;
// Predictions for the form containing the field.
absl::optional<FormPredictions> stored_predictions;

FieldInfo(int driver_id,
autofill::FieldRendererId field_id,
std::string signon_realm,
std::u16string value);
FieldInfo(const FieldInfo&);
FieldInfo& operator=(const FieldInfo&);
~FieldInfo();

friend bool operator==(const FieldInfo& lhs, const FieldInfo& rhs) = default;
};

// TODO(crbug/1468297): Propagate server predictions to the class.
// Manages information about the last user-interacted fields, keeps
// the data and erases it once it becomes stale.
class FieldInfoManager : public KeyedService {
Expand All @@ -62,11 +64,16 @@ class FieldInfoManager : public KeyedService {
~FieldInfoManager() override;

// Caches |info|.
void AddFieldInfo(const FieldInfo& info);
void AddFieldInfo(const FieldInfo& new_info,
const absl::optional<FormPredictions>& predictions);

// Retrieves field info for the given |signon_realm|.
std::vector<FieldInfo> GetFieldInfo(const std::string& signon_realm);

// Propagates signatures and field type received from the server.
void ProcessServerPredictions(
const std::map<autofill::FormSignature, FormPredictions>& predictions);

private:
struct FieldInfoEntry {
// Cached field info.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,49 @@
#include "base/i18n/case_conversion.h"
#include "base/test/task_environment.h"
#include "base/test/test_mock_time_task_runner.h"
#include "components/password_manager/core/browser/form_parsing/password_field_prediction.h"
#include "testing/gtest/include/gtest/gtest.h"

using autofill::FieldRendererId;
using autofill::FieldSignature;
using autofill::FormSignature;
using autofill::ServerFieldType;

namespace password_manager {

namespace {

const char kFirstDomain[] = "https://firstdomain.com";
const char kSecondDomain[] = "https://seconddomain.com";
const char kTestDomain[] = "https://firstdomain.com";
constexpr FormSignature kTestFormSignature(100);
constexpr FieldSignature kTestFieldSignature(200);
constexpr int kTestDriverId = 1;
constexpr FieldRendererId kTestFieldId(1);
constexpr ServerFieldType kTestFieldType = autofill::USERNAME;

const char kAnotherDomain[] = "https://seconddomain.com";
constexpr FormSignature kAnotherFormSignature(300);
constexpr FieldSignature kAnotherFieldSignature(400);
constexpr int kAnotherDriverId = 2;
constexpr FieldRendererId kAnotherFieldId(2);
constexpr ServerFieldType kAnotherFieldType = autofill::PASSWORD;

FormPredictions CreateTestPredictions(int driver_id,
FormSignature form_signature,
FieldSignature field_signature,
FieldRendererId renderer_id,
ServerFieldType type) {
FormPredictions predictions;
predictions.driver_id = driver_id;
predictions.form_signature = form_signature;

PasswordFieldPrediction field_prediction;
field_prediction.signature = field_signature;
field_prediction.renderer_id = renderer_id;
field_prediction.type = type;
predictions.fields.push_back(field_prediction);

return predictions;
}

} // namespace

Expand All @@ -38,55 +69,102 @@ class FieldInfoManagerTest : public testing::Test {
};

TEST_F(FieldInfoManagerTest, InfoAddedRetrievedAndExpired) {
FieldInfo info(/*driver_id=*/1, FieldRendererId(1), kFirstDomain, u"value");
manager_->AddFieldInfo(info);
FieldInfo info(kTestDriverId, kTestFieldId, kTestDomain, u"value");
manager_->AddFieldInfo(info, /*predictions=*/absl::nullopt);
std::vector<FieldInfo> expected_info = {info};
EXPECT_EQ(manager_->GetFieldInfo(kFirstDomain), expected_info);
EXPECT_TRUE(manager_->GetFieldInfo(kSecondDomain).empty());
EXPECT_EQ(manager_->GetFieldInfo(kTestDomain), expected_info);
EXPECT_TRUE(manager_->GetFieldInfo(kAnotherDomain).empty());

// Check that the info is still accessible.
task_environment_.FastForwardBy(kFieldInfoLifetime / 2);
EXPECT_EQ(manager_->GetFieldInfo(kFirstDomain), expected_info);
EXPECT_EQ(manager_->GetFieldInfo(kTestDomain), expected_info);

// The info should not be accessible anymore
task_environment_.FastForwardBy(kFieldInfoLifetime / 2);
EXPECT_TRUE(manager_->GetFieldInfo(kFirstDomain).empty());
EXPECT_TRUE(manager_->GetFieldInfo(kTestDomain).empty());
}

TEST_F(FieldInfoManagerTest, InfoOverwrittenWithNewField) {
FieldInfo info1(/*driver_id=*/1, FieldRendererId(1), kFirstDomain, u"value1");
manager_->AddFieldInfo(info1);
FieldInfo info2(/*driver_id=*/2, FieldRendererId(2), kFirstDomain, u"value2");
manager_->AddFieldInfo(info2);
FieldInfo info1(kTestDriverId, FieldRendererId(1), kTestDomain, u"value1");
manager_->AddFieldInfo(info1, /*predictions=*/absl::nullopt);
FieldInfo info2(kTestDriverId, FieldRendererId(2), kTestDomain, u"value2");
manager_->AddFieldInfo(info2, /*predictions=*/absl::nullopt);

std::vector<FieldInfo> expected_info = {info1, info2};
EXPECT_EQ(manager_->GetFieldInfo(kFirstDomain), expected_info);
EXPECT_EQ(manager_->GetFieldInfo(kTestDomain), expected_info);

// The third info should dismiss the first one.
FieldInfo info3(/*driver_id=*/3, FieldRendererId(3), kFirstDomain, u"value3");
manager_->AddFieldInfo(info3);
FieldInfo info3(kTestDriverId, FieldRendererId(3), kTestDomain, u"value3");
manager_->AddFieldInfo(info3, /*predictions=*/absl::nullopt);

expected_info = {info2, info3};
EXPECT_EQ(manager_->GetFieldInfo(kFirstDomain), expected_info);
EXPECT_EQ(manager_->GetFieldInfo(kTestDomain), expected_info);
}

TEST_F(FieldInfoManagerTest, InfoUpdatedWithNewValue) {
FieldInfo info1(/*driver_id=*/1, FieldRendererId(1), kFirstDomain, u"value");
manager_->AddFieldInfo(info1);
FieldInfo info1(kTestDriverId, kTestFieldId, kTestDomain, u"value");
manager_->AddFieldInfo(info1, /*predictions=*/absl::nullopt);

// The value should not be stored twice for the same field.
FieldInfo info2 = info1;
info2.value = u"new_value";
manager_->AddFieldInfo(info2);
manager_->AddFieldInfo(info2, /*predictions=*/absl::nullopt);

std::vector<FieldInfo> expected_info = {info2};
EXPECT_EQ(manager_->GetFieldInfo(kFirstDomain), expected_info);
EXPECT_EQ(manager_->GetFieldInfo(kTestDomain), expected_info);
}

TEST_F(FieldInfoManagerTest, FieldValueLowercased) {
std::u16string raw_value = u"VaLuE";
FieldInfo info(/*driver_id=*/1, FieldRendererId(1), kFirstDomain, raw_value);
FieldInfo info(kTestDriverId, kTestFieldId, kTestDomain, raw_value);
EXPECT_EQ(info.value, base::i18n::ToLower(raw_value));
}

TEST_F(FieldInfoManagerTest, InfoAddedWithPredictions) {
FieldInfo info(kTestDriverId, kTestFieldId, kTestDomain, u"value");
FormPredictions predictions =
CreateTestPredictions(kTestDriverId, kTestFormSignature,
kTestFieldSignature, kTestFieldId, kTestFieldType);
manager_->AddFieldInfo(info, predictions);

auto field_info_cache = manager_->GetFieldInfo(kTestDomain);
ASSERT_EQ(field_info_cache.size(), 1u);

EXPECT_EQ(field_info_cache[0].stored_predictions, predictions);
EXPECT_EQ(field_info_cache[0].type, kTestFieldType);
}

TEST_F(FieldInfoManagerTest, ProcessServerPredictions) {
FieldInfo info(kTestDriverId, kTestFieldId, kTestDomain, u"value");
manager_->AddFieldInfo(info, /*predictions=*/absl::nullopt);

// Create test predictions.
std::map<autofill::FormSignature, FormPredictions> predictions;
FormPredictions form_prediction =
CreateTestPredictions(kTestDriverId, kTestFormSignature,
kTestFieldSignature, kTestFieldId, kTestFieldType);

// Add another field.
PasswordFieldPrediction another_field_prediction;
another_field_prediction.renderer_id = kAnotherFieldId;
another_field_prediction.type = kAnotherFieldType;
form_prediction.fields.push_back(another_field_prediction);

predictions[kTestFormSignature] = form_prediction;

// Add a prediction with the same field id, but different driver.
FormPredictions different_driver_prediction = CreateTestPredictions(
kAnotherDriverId, kAnotherFormSignature, kAnotherFieldSignature,
kTestFieldId, kAnotherFieldType);
predictions[kAnotherFormSignature] = different_driver_prediction;

manager_->ProcessServerPredictions(predictions);

auto field_info_cache = manager_->GetFieldInfo(kTestDomain);
ASSERT_EQ(field_info_cache.size(), 1u);

EXPECT_EQ(field_info_cache[0].stored_predictions, form_prediction);
EXPECT_EQ(field_info_cache[0].type, kTestFieldType);
}

} // namespace password_manager
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ struct PasswordFieldPrediction {
autofill::FieldSignature signature;
autofill::ServerFieldType type;
bool may_use_prefilled_placeholder = false;

friend bool operator==(const PasswordFieldPrediction& lhs,
const PasswordFieldPrediction& rhs) = default;
};

// Contains server predictions for a form.
Expand All @@ -53,6 +56,9 @@ struct FormPredictions {

autofill::FormSignature form_signature;
std::vector<PasswordFieldPrediction> fields;

friend bool operator==(const FormPredictions& lhs,
const FormPredictions& rhs) = default;
};

// Extracts password related server predictions from `form` and `predictions`.
Expand Down
41 changes: 30 additions & 11 deletions components/password_manager/core/browser/password_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,8 @@ void PasswordManager::OnUserModifiedNonPasswordField(
}
field_info_manager->AddFieldInfo(
{driver_id, renderer_id, GetSignonRealm(driver->GetLastCommittedURL()),
value});
value},
FindPredictionsForField(renderer_id, driver_id));
}
}

Expand Down Expand Up @@ -1276,6 +1277,15 @@ void PasswordManager::ProcessAutofillPredictions(
password_generation_manager->ProcessPasswordRequirements(forms,
predictions);
}

// Process predictions in case they arrived after the user interacted with
// potential username fields.
FieldInfoManager* field_info_manager = client_->GetFieldInfoManager();
// The manager might not exist in incognito.
if (!field_info_manager) {
return;
}
field_info_manager->ProcessServerPredictions(predictions_);
}

PasswordFormManager* PasswordManager::GetSubmittedManager() const {
Expand Down Expand Up @@ -1355,20 +1365,29 @@ PasswordFormManager* PasswordManager::GetMatchedManager(
return nullptr;
}

void PasswordManager::TryToFindPredictionsToPossibleUsernameData() {
if (!possible_username_ || possible_username_->form_predictions)
return;

for (auto it : predictions_) {
if (it.second.driver_id != possible_username_->driver_id)
absl::optional<FormPredictions> PasswordManager::FindPredictionsForField(
FieldRendererId field_id,
int driver_id) {
for (const auto& form : predictions_) {
if (form.second.driver_id != driver_id) {
continue;
for (const PasswordFieldPrediction& field : it.second.fields) {
if (field.renderer_id == possible_username_->renderer_id) {
possible_username_->form_predictions = it.second;
return;
}
for (const PasswordFieldPrediction& field : form.second.fields) {
if (field.renderer_id == field_id) {
return form.second;
}
}
}
return absl::nullopt;
}

void PasswordManager::TryToFindPredictionsToPossibleUsernameData() {
if (!possible_username_ || possible_username_->form_predictions) {
return;
}

possible_username_->form_predictions = FindPredictionsForField(
possible_username_->renderer_id, possible_username_->driver_id);
}

void PasswordManager::ShowManualFallbackForSaving(
Expand Down

0 comments on commit 32ef22b

Please sign in to comment.