blob: a1857cf4c4169c74252b44c4d62764a58e108089 [file] [log] [blame]
// Copyright 2024 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/ai/ai_data_keyed_service.h"
#include <string>
#include "base/functional/bind.h"
#include "base/functional/callback_forward.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/ai/ai_data_keyed_service_factory.h"
#include "chrome/browser/autofill_ai/chrome_autofill_ai_client.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "chrome/browser/ui/tabs/tab_group.h"
#include "chrome/browser/ui/tabs/tab_group_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/web_applications/isolated_web_apps/test/isolated_web_app_builder.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/autofill/core/common/autofill_prefs.h"
#include "components/autofill/core/common/form_data.h"
#include "components/autofill_ai/core/browser/autofill_ai_features.h"
#include "components/autofill_ai/core/browser/suggestion/autofill_ai_model_executor.h"
#include "components/network_session_configurator/common/network_switches.h"
#include "components/optimization_guide/proto/features/common_quality_data.pb.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/fenced_frame_test_util.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/request_handler_util.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using ::testing::ReturnRef;
class MockAutofillAiModelExecutor
: public autofill_ai::AutofillAiModelExecutor {
public:
MOCK_METHOD(
void,
GetPredictions,
(autofill::FormData form_data,
(base::flat_map<autofill::FieldGlobalId, bool> field_eligibility_map),
(base::flat_map<autofill::FieldGlobalId, bool> sensitivity_map),
optimization_guide::proto::features::AXTreeUpdate ax_tree_update,
PredictionsReceivedCallback callback),
(override));
MOCK_METHOD(
const std::optional<
optimization_guide::proto::features::FormsPredictionsRequest>&,
GetLatestRequest,
(),
(const override));
MOCK_METHOD(
const std::optional<
optimization_guide::proto::features::FormsPredictionsResponse>&,
GetLatestResponse,
(),
(const override));
};
class AiDataKeyedServiceBrowserTest : public InProcessBrowserTest {
public:
AiDataKeyedServiceBrowserTest() = default;
AiDataKeyedServiceBrowserTest(const AiDataKeyedServiceBrowserTest&) = delete;
AiDataKeyedServiceBrowserTest& operator=(
const AiDataKeyedServiceBrowserTest&) = delete;
~AiDataKeyedServiceBrowserTest() override = default;
void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1");
https_server_ = std::make_unique<net::EmbeddedTestServer>(
net::EmbeddedTestServer::TYPE_HTTPS);
https_server_->AddDefaultHandlers(GetChromeTestDataDir());
ASSERT_TRUE(https_server_->Start());
url_ = https_server_->GetURL("/simple.html");
}
void SetAiData(base::OnceClosure quit_closure,
AiDataKeyedService::AiData ai_data) {
ai_data_ = std::move(ai_data);
std::move(quit_closure).Run();
}
GURL url() { return url_; }
const AiDataKeyedService::AiData& ai_data() { return ai_data_; }
void LoadSimplePageAndData() {
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
content::NavigateToURLBlockUntilNavigationsComplete(web_contents, url_, 1);
AiDataKeyedService* ai_data_service =
AiDataKeyedServiceFactory::GetAiDataKeyedService(browser()->profile());
base::RunLoop run_loop;
auto dom_node_id = 0;
ai_data_service->GetAiDataWithSpecifiers(
1, dom_node_id, web_contents, "test",
base::BindOnce(&AiDataKeyedServiceBrowserTest::SetAiData,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
DCHECK(ai_data());
}
private:
GURL url_;
std::unique_ptr<net::EmbeddedTestServer> https_server_;
AiDataKeyedService::AiData ai_data_;
};
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest,
AllowlistedExtensionList) {
std::vector<std::string> expected_allowlisted_extensions = {
"hpkopmikdojpadgmioifjjodbmnjjjca", "bgbpcgpcobgjpnpiginpidndjpggappi"};
EXPECT_EQ(AiDataKeyedService::GetAllowlistedExtensions(),
expected_allowlisted_extensions);
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, GetsData) {
LoadSimplePageAndData();
EXPECT_TRUE(ai_data());
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, InnerText) {
LoadSimplePageAndData();
ASSERT_TRUE(ai_data());
EXPECT_EQ(ai_data()->page_context().inner_text(), "Non empty simple page");
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, InnerTextOffset) {
LoadSimplePageAndData();
ASSERT_TRUE(ai_data());
EXPECT_EQ(ai_data()->page_context().inner_text_offset(), 0u);
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, Title) {
LoadSimplePageAndData();
ASSERT_TRUE(ai_data());
EXPECT_EQ(ai_data()->page_context().title(), "OK");
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, Url) {
LoadSimplePageAndData();
ASSERT_TRUE(ai_data());
EXPECT_NE(ai_data()->page_context().url().find("simple"), std::string::npos);
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, AxTreeUpdate) {
LoadSimplePageAndData();
ASSERT_TRUE(ai_data());
// If there are nodes and the titles is correct, then the AX tree is filled
// out.
EXPECT_GT(ai_data()->page_context().ax_tree_data().nodes().size(), 0);
EXPECT_EQ(ai_data()->page_context().ax_tree_data().tree_data().title(), "OK");
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, TabData) {
chrome::AddTabAt(browser(), GURL("foo.com"), -1, false);
chrome::AddTabAt(browser(), GURL("bar.com"), -1, false);
auto* tab_group1 = browser()->GetTabStripModel()->group_model()->GetTabGroup(
browser()->GetTabStripModel()->AddToNewGroup({0}));
auto vis_data1 = *tab_group1->visual_data();
vis_data1.SetTitle(u"ok");
tab_group1->SetVisualData(vis_data1);
auto* tab_group2 = browser()->GetTabStripModel()->group_model()->GetTabGroup(
browser()->GetTabStripModel()->AddToNewGroup({1, 2}));
auto vis_data2 = *tab_group1->visual_data();
vis_data2.SetTitle(u"ok");
tab_group2->SetVisualData(vis_data2);
LoadSimplePageAndData();
ASSERT_TRUE(ai_data());
EXPECT_EQ(ai_data()->active_tab_id(), 0);
EXPECT_EQ(ai_data()->tabs().size(), 3);
EXPECT_EQ(ai_data()->pre_existing_tab_groups().size(), 2);
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, TabInnerText) {
chrome::AddTabAt(browser(), GURL("foo.com"), -1, false);
chrome::AddTabAt(browser(), GURL("bar.com"), -1, false);
auto* tab_group1 = browser()->GetTabStripModel()->group_model()->GetTabGroup(
browser()->GetTabStripModel()->AddToNewGroup({0}));
auto vis_data1 = *tab_group1->visual_data();
vis_data1.SetTitle(u"ok");
tab_group1->SetVisualData(vis_data1);
auto* tab_group2 = browser()->GetTabStripModel()->group_model()->GetTabGroup(
browser()->GetTabStripModel()->AddToNewGroup({1, 2}));
auto vis_data2 = *tab_group1->visual_data();
vis_data2.SetTitle(u"ok");
tab_group2->SetVisualData(vis_data2);
LoadSimplePageAndData();
ASSERT_TRUE(ai_data());
EXPECT_EQ(ai_data()->active_tab_id(), 0);
for (const auto& tab_in_proto : ai_data()->tabs()) {
if (tab_in_proto.tab_id() == 0) {
EXPECT_EQ(tab_in_proto.title(), "OK");
EXPECT_NE(tab_in_proto.url().find("simple"), std::string::npos);
EXPECT_EQ(tab_in_proto.page_context().inner_text(),
"Non empty simple page");
}
}
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, TabInnerTextLimit) {
LoadSimplePageAndData();
chrome::AddTabAt(browser(), GURL("bar.com"), -1, true);
LoadSimplePageAndData();
EXPECT_EQ(ai_data()->active_tab_id(), 1);
for (auto& tab : ai_data()->tabs()) {
if (tab.tab_id() == 0) {
EXPECT_EQ(tab.page_context().inner_text(), "Non empty simple page");
}
if (tab.tab_id() == 1) {
EXPECT_EQ(tab.page_context().inner_text(), "");
}
}
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, Screenshot) {
LoadSimplePageAndData();
content::RequestFrame(browser()->tab_strip_model()->GetActiveWebContents());
EXPECT_NE(ai_data()->page_context().tab_screenshot(), "");
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, SiteEngagementScores) {
LoadSimplePageAndData();
EXPECT_EQ(ai_data()->site_engagement().entries().size(), 1);
EXPECT_NE(ai_data()->site_engagement().entries()[0].url(), "");
EXPECT_GE(ai_data()->site_engagement().entries()[0].score(), 0);
}
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTest, AIPageContent) {
LoadSimplePageAndData();
const auto& page_content = ai_data()->page_context().annotated_page_content();
const auto& content_attributes =
page_content.root_node().content_attributes();
EXPECT_EQ(content_attributes.attribute_type(),
optimization_guide::proto::features::CONTENT_ATTRIBUTE_ROOT);
}
#if !BUILDFLAG(IS_ANDROID)
class AiDataKeyedServiceBrowserTestWithFormsPredictions
: public AiDataKeyedServiceBrowserTest {
public:
~AiDataKeyedServiceBrowserTestWithFormsPredictions() override = default;
AiDataKeyedServiceBrowserTestWithFormsPredictions() {
scoped_feature_list_.InitAndEnableFeature(autofill_ai::kAutofillAi);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTestWithFormsPredictions,
GetFormsPredictionsDataForModelPrototyping) {
browser()->profile()->GetPrefs()->SetBoolean(
autofill::prefs::kAutofillPredictionImprovementsEnabled, true);
// Set up test data.
auto request = std::make_optional<
optimization_guide::proto::features::FormsPredictionsRequest>();
optimization_guide::proto::features::UserAnnotationsEntry* entry =
request->add_entries();
entry->set_key("test_key");
entry->set_value("test_value");
auto response = std::make_optional<
optimization_guide::proto::features::FormsPredictionsResponse>();
optimization_guide::proto::features::FilledFormData* filled_form_data =
response->mutable_form_data();
optimization_guide::proto::features::FilledFormFieldData* filled_field =
filled_form_data->add_filled_form_field_data();
filled_field->set_normalized_label("test_label");
// Set up mock.
auto mock_autofill_ai_model_executor =
std::make_unique<MockAutofillAiModelExecutor>();
EXPECT_CALL(*mock_autofill_ai_model_executor, GetLatestRequest)
.WillOnce(ReturnRef(request));
EXPECT_CALL(*mock_autofill_ai_model_executor, GetLatestResponse)
.WillOnce(ReturnRef(response));
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
tabs::TabInterface* tab = tabs::TabInterface::GetFromContents(web_contents);
ASSERT_TRUE(tab)
<< "Active WebContents isn't a tab. TabInterface::GetFromContents() "
"was expected to crash.";
ChromeAutofillAiClient* client =
tab->GetTabFeatures()->chrome_autofill_ai_client();
ASSERT_TRUE(client)
<< "TabFeatures hasn't created ChromeAutofillAiClient yet.";
client->SetModelExecutorForTesting(
std::move(mock_autofill_ai_model_executor));
LoadSimplePageAndData();
ASSERT_TRUE(ai_data());
ASSERT_EQ(ai_data()->forms_predictions_request().entries().size(), 1);
EXPECT_EQ(ai_data()->forms_predictions_request().entries()[0].key(),
"test_key");
EXPECT_EQ(ai_data()->forms_predictions_request().entries()[0].value(),
"test_value");
ASSERT_EQ(ai_data()
->forms_predictions_response()
.form_data()
.filled_form_field_data()
.size(),
1);
EXPECT_EQ(ai_data()
->forms_predictions_response()
.form_data()
.filled_form_field_data()[0]
.normalized_label(),
"test_label");
}
#endif
class AiDataKeyedServiceBrowserTestWithBlocklistedExtensions
: public AiDataKeyedServiceBrowserTest {
public:
~AiDataKeyedServiceBrowserTestWithBlocklistedExtensions() override = default;
AiDataKeyedServiceBrowserTestWithBlocklistedExtensions() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
AiDataKeyedService::GetAllowlistedAiDataExtensionsFeatureForTesting(),
{{"blocked_extension_ids", "hpkopmikdojpadgmioifjjodbmnjjjca"}});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTestWithBlocklistedExtensions,
BlockedExtensionList) {
std::vector<std::string> expected_allowlisted_extensions = {
"bgbpcgpcobgjpnpiginpidndjpggappi"};
EXPECT_EQ(AiDataKeyedService::GetAllowlistedExtensions(),
expected_allowlisted_extensions);
}
class AiDataKeyedServiceBrowserTestWithRemotelyAllowlistedExtensions
: public AiDataKeyedServiceBrowserTest {
public:
~AiDataKeyedServiceBrowserTestWithRemotelyAllowlistedExtensions() override =
default;
AiDataKeyedServiceBrowserTestWithRemotelyAllowlistedExtensions() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
AiDataKeyedService::GetAllowlistedAiDataExtensionsFeatureForTesting(),
{{"allowlisted_extension_ids", "1234"}});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(
AiDataKeyedServiceBrowserTestWithRemotelyAllowlistedExtensions,
RemotelyAllowlistedExtensionList) {
std::vector<std::string> expected_allowlisted_extensions = {
"1234",
"hpkopmikdojpadgmioifjjodbmnjjjca",
"bgbpcgpcobgjpnpiginpidndjpggappi",
};
EXPECT_EQ(AiDataKeyedService::GetAllowlistedExtensions(),
expected_allowlisted_extensions);
}
class AiDataKeyedServiceBrowserTestWithAllowAndBlock
: public AiDataKeyedServiceBrowserTest {
public:
~AiDataKeyedServiceBrowserTestWithAllowAndBlock() override = default;
AiDataKeyedServiceBrowserTestWithAllowAndBlock() {
scoped_feature_list_.InitAndEnableFeatureWithParameters(
AiDataKeyedService::GetAllowlistedAiDataExtensionsFeatureForTesting(),
{{"allowlisted_extension_ids", "1234"},
{"blocked_extension_ids", "1234"}});
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(AiDataKeyedServiceBrowserTestWithAllowAndBlock,
AllowAndBlock) {
std::vector<std::string> expected_allowlisted_extensions = {
"hpkopmikdojpadgmioifjjodbmnjjjca", "bgbpcgpcobgjpnpiginpidndjpggappi"};
EXPECT_EQ(AiDataKeyedService::GetAllowlistedExtensions(),
expected_allowlisted_extensions);
}
} // namespace