blob: 499eb86451a2a04cacc9e69f5065766a7ba984fa [file] [log] [blame]
// Copyright 2023 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/compose/compose_text_usage_logger.h"
#include <memory>
#include <string>
#include "base/memory/ptr_util.h"
#include "base/test/task_environment.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "components/autofill/content/browser/content_autofill_driver.h"
#include "components/autofill/content/browser/test_autofill_client_injector.h"
#include "components/autofill/content/browser/test_autofill_driver_injector.h"
#include "components/autofill/content/browser/test_autofill_manager_injector.h"
#include "components/autofill/content/browser/test_content_autofill_client.h"
#include "components/autofill/core/browser/autofill_test_utils.h"
#include "components/autofill/core/browser/browser_autofill_manager.h"
#include "components/autofill/core/browser/form_structure.h"
#include "components/autofill/core/browser/test_browser_autofill_manager.h"
#include "components/autofill/core/common/autofill_test_utils.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill/core/common/unique_ids.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/web_contents.h"
#include "services/metrics/public/cpp/ukm_source.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace compose {
namespace {
using autofill::ContentAutofillDriver;
using autofill::FormControlType;
using autofill::FormData;
using autofill::TestAutofillClientInjector;
using autofill::TestAutofillDriverInjector;
using autofill::TestAutofillManagerInjector;
using autofill::TestBrowserAutofillManager;
using autofill::TestContentAutofillClient;
using autofill::test::CreateTestFormField;
using autofill::test::MakeFieldGlobalId;
using autofill::test::MakeFormGlobalId;
using content::RenderFrameHostImpl;
using content::RenderViewHostTestHarness;
using content::WebContents;
FormData CreateForm(
FormControlType control_type = FormControlType::kInputText) {
FormData form;
form.url = GURL("https://www.foo.com");
form.fields = {
CreateTestFormField("Field one:", "text_value", /*value=*/"",
control_type),
CreateTestFormField("Field two:", "text_value_two",
/*value=*/"", control_type),
CreateTestFormField("Field three:", "text_value_three",
/*value=*/"", control_type),
};
return form;
}
class ComposeTextUsageLoggerTest : public ChromeRenderViewHostTestHarness {
public:
ComposeTextUsageLoggerTest() = default;
ComposeTextUsageLoggerTest(ComposeTextUsageLoggerTest&) = delete;
ComposeTextUsageLoggerTest& operator=(const ComposeTextUsageLoggerTest&) =
delete;
~ComposeTextUsageLoggerTest() override = default;
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
NavigateAndCommit(GURL("https://a.com/"));
ukm_source_id_ = main_rfh()->GetPageUkmSourceId();
}
protected:
std::vector<ukm::TestUkmRecorder::HumanReadableUkmEntry> LoggedTextUsage() {
return ukm_recorder_.GetEntries(
"Compose.TextElementUsage",
{"AutofillFormControlType", "IsAutofillFieldType",
"TypedCharacterCount", "TypedWordCount"});
}
TestBrowserAutofillManager* autofill_manager() {
return autofill_manager_injector_[main_rfh()];
}
ComposeTextUsageLogger* logger() {
return ComposeTextUsageLogger::GetOrCreateForCurrentDocument(main_rfh());
}
void SimulateTyping(autofill::FormGlobalId form_id,
autofill::FieldGlobalId field_id,
std::u16string_view text_value,
int start_index = 0,
int chars_at_a_time = 1) {
size_t index = start_index;
while (index < text_value.size()) {
index = std::min(index + chars_at_a_time, text_value.size());
logger()->OnAfterTextFieldDidChange(
*autofill_manager(), form_id, field_id,
std::u16string(text_value.substr(0, index)));
}
}
autofill::test::AutofillUnitTestEnvironment autofill_test_environment_;
TestAutofillClientInjector<TestContentAutofillClient>
autofill_client_injector_;
TestAutofillDriverInjector<ContentAutofillDriver> autofill_driver_injector_;
TestAutofillManagerInjector<TestBrowserAutofillManager>
autofill_manager_injector_;
ukm::TestAutoSetUkmRecorder ukm_recorder_;
ukm::SourceId ukm_source_id_;
};
TEST_F(ComposeTextUsageLoggerTest, TextFieldEntry) {
FormData form_data = CreateForm(FormControlType::kInputText);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kInputText)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8},
{"TypedWordCount", 2},
})));
}
TEST_F(ComposeTextUsageLoggerTest, TextAreaEntry) {
FormData form_data = CreateForm(FormControlType::kTextArea);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8},
{"TypedWordCount", 2},
})));
}
TEST_F(ComposeTextUsageLoggerTest, FormNotFound) {
// Not calling AddSeenFormStructure(), so the form won't be found.
FormData form_data = CreateForm(FormControlType::kInputText);
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_, {
{"AutofillFormControlType", -1},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8},
{"TypedWordCount", 2},
})));
}
TEST_F(ComposeTextUsageLoggerTest, SensitiveFieldEntry) {
FormData form_data = CreateForm();
auto form_structure = std::make_unique<autofill::FormStructure>(form_data);
form_structure->field(0)->SetTypeTo(
autofill::AutofillType(autofill::FieldType::CREDIT_CARD_NAME_FIRST));
autofill_manager()->AddSeenFormStructure(std::move(form_structure));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kInputText)},
{"IsAutofillFieldType", 1},
{"TypedCharacterCount", -1},
{"TypedWordCount", -1},
})));
}
TEST_F(ComposeTextUsageLoggerTest, NonSensitiveAutofillFieldType) {
FormData form_data = CreateForm();
auto form_structure = std::make_unique<autofill::FormStructure>(form_data);
form_structure->field(0)->SetTypeTo(
autofill::AutofillType(autofill::FieldType::ADDRESS_HOME_ADDRESS));
autofill_manager()->AddSeenFormStructure(std::move(form_structure));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kInputText)},
{"IsAutofillFieldType", 1},
{"TypedCharacterCount", 8},
{"TypedWordCount", 2},
})));
}
TEST_F(ComposeTextUsageLoggerTest, OnlyLastChangeIsLogged) {
FormData form_data = CreateForm(FormControlType::kTextArea);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"One two three four");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 16 /*18 rounded down*/},
{"TypedWordCount", 4},
})));
}
TEST_F(ComposeTextUsageLoggerTest, LastChangeClearsField) {
FormData form_data = CreateForm(FormControlType::kTextArea);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
logger()->OnAfterTextFieldDidChange(*autofill_manager(),
form_data.global_id(),
form_data.fields[0].global_id(), u"");
DeleteContents();
// Nothing logged.
EXPECT_THAT(LoggedTextUsage(), testing::IsEmpty());
}
TEST_F(ComposeTextUsageLoggerTest, FieldNotEmptyAtStart) {
FormData form_data = CreateForm(FormControlType::kTextArea);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"This is some longer text that exists in the field. New text"
u" is now written here !!!!",
/*start_index=*/50);
DeleteContents();
EXPECT_THAT(
LoggedTextUsage(),
testing::UnorderedElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 32},
{"TypedWordCount", 4},
})));
}
TEST_F(ComposeTextUsageLoggerTest, CantWriteMoreCharactersThanExistInField) {
FormData form_data = CreateForm(FormControlType::kTextArea);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
// Types 0123456789 three times, replacing the field contents each time.
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"0123456789");
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"0123456789");
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"0123456789");
DeleteContents();
EXPECT_THAT(
LoggedTextUsage(),
testing::UnorderedElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8 /*10 rounded down*/},
{"TypedWordCount", 1},
})));
}
TEST_F(ComposeTextUsageLoggerTest, TwoFieldsModified) {
FormData form_data = CreateForm(FormControlType::kTextArea);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
SimulateTyping(form_data.global_id(), form_data.fields[1].global_id(),
u"One two three four");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::UnorderedElementsAre(
ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8 /*9 rounded down*/},
{"TypedWordCount", 2},
}),
ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 16 /*18 rounded down*/},
{"TypedWordCount", 4},
})));
}
TEST_F(ComposeTextUsageLoggerTest, CountingWordsCorrectly) {
FormData form_data = CreateForm(FormControlType::kTextArea);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(), u" ");
SimulateTyping(form_data.global_id(), form_data.fields[1].global_id(),
u"\r\n hi\tmom\r");
SimulateTyping(form_data.global_id(), form_data.fields[2].global_id(),
u" word");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::UnorderedElementsAre(
ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 1},
{"TypedWordCount", 0},
}),
ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 4 /*5 rounded down*/},
{"TypedWordCount", 1},
}),
ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8 /*10 rounded down*/},
{"TypedWordCount", 2},
})));
}
TEST_F(ComposeTextUsageLoggerTest, ContentEditableEntry) {
FormData form_data = CreateForm(FormControlType::kContentEditable);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kContentEditable)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8},
{"TypedWordCount", 2},
})));
}
TEST_F(ComposeTextUsageLoggerTest, ContentEditableFormNotFound) {
// Not calling AddSeenFormStructure(), so the form won't be found.
FormData form_data = CreateForm(FormControlType::kContentEditable);
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
DeleteContents();
EXPECT_THAT(LoggedTextUsage(),
testing::ElementsAre(ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_, {
{"AutofillFormControlType", -1},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8},
{"TypedWordCount", 2},
})));
}
TEST_F(ComposeTextUsageLoggerTest, TwoTypesOfFormsModified) {
FormData form_data = CreateForm(FormControlType::kTextArea);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(form_data));
SimulateTyping(form_data.global_id(), form_data.fields[0].global_id(),
u"Some text");
SimulateTyping(form_data.global_id(), form_data.fields[1].global_id(),
u"One two three four");
FormData content_editable_form_data =
CreateForm(FormControlType::kContentEditable);
autofill_manager()->AddSeenFormStructure(
std::make_unique<autofill::FormStructure>(content_editable_form_data));
SimulateTyping(content_editable_form_data.global_id(),
content_editable_form_data.fields[0].global_id(),
u"Some text");
SimulateTyping(content_editable_form_data.global_id(),
content_editable_form_data.fields[1].global_id(),
u"One two three four");
DeleteContents();
EXPECT_THAT(
LoggedTextUsage(),
testing::UnorderedElementsAre(
ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8 /*9 rounded down*/},
{"TypedWordCount", 2},
}),
ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kTextArea)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 16 /*18 rounded down*/},
{"TypedWordCount", 4},
}),
ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kContentEditable)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 8 /*9 rounded down*/},
{"TypedWordCount", 2},
}),
ukm::TestUkmRecorder::HumanReadableUkmEntry(
ukm_source_id_,
{
{"AutofillFormControlType",
static_cast<int64_t>(FormControlType::kContentEditable)},
{"IsAutofillFieldType", 0},
{"TypedCharacterCount", 16 /*18 rounded down*/},
{"TypedWordCount", 4},
})));
}
} // namespace
} // namespace compose