blob: 2841e88cabf297badd450d39c59a69187eaeeba8 [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdint.h>
#include <memory>
#include <optional>
#include <utility>
#include "base/feature_list.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/json/values_util.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/thread_pool.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/test_future.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/history/history_service_factory.h"
#include "chrome/browser/notifications/metrics/mock_notification_metrics_logger.h"
#include "chrome/browser/notifications/metrics/notification_metrics_logger_factory.h"
#include "chrome/browser/notifications/notification_display_service_impl.h"
#include "chrome/browser/notifications/notification_display_service_tester.h"
#include "chrome/browser/notifications/platform_notification_service_factory.h"
#include "chrome/browser/notifications/platform_notification_service_impl.h"
#include "chrome/browser/safe_browsing/notification_content_detection/mock_notification_content_detection_service.h"
#include "chrome/browser/safe_browsing/notification_content_detection/notification_content_detection_service_factory.h"
#include "chrome/browser/ui/safety_hub/disruptive_notification_permissions_manager.h"
#include "chrome/common/chrome_features.h"
#include "chrome/test/base/testing_profile.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/test_history_database.h"
#include "components/safe_browsing/content/browser/notification_content_detection/notification_content_detection_constants.h"
#include "components/safe_browsing/content/browser/notification_content_detection/test_model_observer_tracker.h"
#include "components/safe_browsing/core/common/features.h"
#include "components/site_engagement/content/site_engagement_service.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/browser/platform_notification_context.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/storage_partition_config.h"
#include "content/public/test/browser_task_environment.h"
#include "extensions/buildflags/buildflags.h"
#include "services/metrics/public/cpp/ukm_builders.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/notifications/notification_resources.h"
#include "third_party/blink/public/common/notifications/platform_notification_data.h"
#include "third_party/blink/public/mojom/notifications/notification.mojom.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "ui/gfx/image/image_skia_rep.h"
#include "url/gurl.h"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "base/values.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/test_extension_system.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
#if !BUILDFLAG(IS_ANDROID)
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/test/fake_web_app_provider.h"
#include "chrome/browser/web_applications/test/os_integration_test_override_impl.h"
#include "chrome/browser/web_applications/test/web_app_icon_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_install_test_utils.h"
#include "chrome/browser/web_applications/test/web_app_test_utils.h"
#include "chrome/browser/web_applications/web_app_command_scheduler.h"
#include "chrome/browser/web_applications/web_app_icon_generator.h"
#include "chrome/browser/web_applications/web_app_icon_manager.h"
#include "chrome/browser/web_applications/web_app_install_info.h"
#include "chrome/browser/web_applications/web_app_install_params.h"
#include "chrome/browser/web_applications/web_app_provider.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "components/webapps/browser/install_result_code.h"
#include "components/webapps/browser/installable/installable_metrics.h"
#endif
using blink::NotificationResources;
using blink::PlatformNotificationData;
using content::NotificationDatabaseData;
using message_center::Notification;
using ::testing::_;
namespace {
const int kNotificationVibrationPattern[] = { 100, 200, 300 };
const char kNotificationId[] = "my-notification-id";
const char kClosedReason[] = "ClosedReason";
const char kDidReplaceAnotherNotification[] = "DidReplaceAnotherNotification";
const char kHasBadge[] = "HasBadge";
const char kHasImage[] = "HasImage";
const char kHasIcon[] = "HasIcon";
const char kHasRenotify[] = "HasRenotify";
const char kHasTag[] = "HasTag";
const char kIsSilent[] = "IsSilent";
const char kNumActions[] = "NumActions";
const char kNumClicks[] = "NumClicks";
const char kNumActionButtonClicks[] = "NumActionButtonClicks";
const char kRequireInteraction[] = "RequireInteraction";
const char kTimeUntilCloseMillis[] = "TimeUntilClose";
const char kTimeUntilFirstClickMillis[] = "TimeUntilFirstClick";
const char kTimeUntilLastClickMillis[] = "TimeUntilLastClick";
} // namespace
class PlatformNotificationServiceTest : public testing::Test {
public:
void SetUp() override {
scoped_feature_list_.InitWithFeatures(
{}, {safe_browsing::kReportNotificationContentDetectionData});
TestingProfile::Builder profile_builder;
profile_builder.AddTestingFactory(
HistoryServiceFactory::GetInstance(),
HistoryServiceFactory::GetDefaultFactory());
profile_ = profile_builder.Build();
display_service_tester_ =
std::make_unique<NotificationDisplayServiceTester>(profile_.get());
mock_logger_ = static_cast<MockNotificationMetricsLogger*>(
NotificationMetricsLoggerFactory::GetInstance()
->SetTestingFactoryAndUse(
profile_.get(),
base::BindRepeating(
&MockNotificationMetricsLogger::FactoryForTests)));
recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
void TearDown() override {
display_service_tester_.reset();
mock_logger_ = nullptr;
profile_.reset();
}
content::PlatformNotificationContext* GetPlatformNotificationContext(
GURL origin) {
return profile_->GetStoragePartitionForUrl(origin)
->GetPlatformNotificationContext();
}
NotificationDatabaseData ReadNotificationDataAndRecordInteractionSynchronous(
content::PlatformNotificationContext* context,
const std::string& notification_id,
const GURL& origin) {
base::RunLoop run_loop;
context->ReadNotificationDataAndRecordInteraction(
"p#" + origin.spec() + "#0" + notification_id, origin,
content::PlatformNotificationContext::Interaction::NONE,
base::BindOnce(
&PlatformNotificationServiceTest::
DidReadNotificationDataAndRecordInteractionSynchronous,
base::Unretained(this), run_loop.QuitClosure()));
run_loop.Run();
return notification_database_data_;
}
void DidReadNotificationDataAndRecordInteractionSynchronous(
base::OnceClosure quit_closure,
bool success,
const NotificationDatabaseData& data) {
ASSERT_TRUE(success);
notification_database_data_ = data;
std::move(quit_closure).Run();
}
protected:
// Returns the Platform Notification Service these unit tests are for.
PlatformNotificationServiceImpl* service() {
return PlatformNotificationServiceFactory::GetForProfile(profile_.get());
}
size_t GetNotificationCountForType(NotificationHandler::Type type) {
return display_service_tester_->GetDisplayedNotificationsForType(type)
.size();
}
Notification GetDisplayedNotificationForType(NotificationHandler::Type type) {
std::vector<Notification> notifications =
display_service_tester_->GetDisplayedNotificationsForType(type);
DCHECK_EQ(1u, notifications.size());
return notifications[0];
}
protected:
// This needs to be declared before `task_environment_`, so that it is
// destroyed after `task_environment_` - this ensures that tasks running on
// other threads won't access `scoped_feature_list_` while it is being
// destroyed.
base::test::ScopedFeatureList scoped_feature_list_;
content::BrowserTaskEnvironment task_environment_;
std::unique_ptr<TestingProfile> profile_;
std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
// Owned by the |profile_| as a keyed service.
raw_ptr<MockNotificationMetricsLogger> mock_logger_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> recorder_;
private:
NotificationDatabaseData notification_database_data_;
};
TEST_F(PlatformNotificationServiceTest, DisplayNonPersistentThenClose) {
PlatformNotificationData data;
data.title = u"My Notification";
data.body = u"Hello, world!";
service()->DisplayNotification(kNotificationId, GURL("https://chrome.com/"),
/*document_url=*/GURL(), data,
NotificationResources());
EXPECT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
service()->CloseNotification(kNotificationId);
EXPECT_EQ(0u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
}
TEST_F(PlatformNotificationServiceTest, DisplayPersistentThenClose) {
PlatformNotificationData data;
data.title = u"My notification's title";
data.body = u"Hello, world!";
EXPECT_CALL(*mock_logger_, LogPersistentNotificationShown());
service()->DisplayPersistentNotification(
kNotificationId, GURL() /* service_worker_scope */,
GURL("https://chrome.com/"), data, NotificationResources());
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
Notification notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_PERSISTENT);
EXPECT_EQ("https://chrome.com/", notification.origin_url().spec());
EXPECT_EQ("My notification's title",
base::UTF16ToUTF8(notification.title()));
EXPECT_EQ("Hello, world!",
base::UTF16ToUTF8(notification.message()));
service()->ClosePersistentNotification(kNotificationId);
EXPECT_EQ(0u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
}
TEST_F(PlatformNotificationServiceTest, DisplayNonPersistentPropertiesMatch) {
std::vector<int> vibration_pattern(std::begin(kNotificationVibrationPattern),
std::end(kNotificationVibrationPattern));
PlatformNotificationData data;
data.title = u"My notification's title";
data.body = u"Hello, world!";
data.vibration_pattern = vibration_pattern;
data.silent = true;
service()->DisplayNotification(kNotificationId, GURL("https://chrome.com/"),
/*document_url=*/GURL(), data,
NotificationResources());
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
Notification notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_NON_PERSISTENT);
EXPECT_EQ("https://chrome.com/", notification.origin_url().spec());
EXPECT_EQ("My notification's title",
base::UTF16ToUTF8(notification.title()));
EXPECT_EQ("Hello, world!",
base::UTF16ToUTF8(notification.message()));
EXPECT_THAT(notification.vibration_pattern(),
testing::ElementsAreArray(kNotificationVibrationPattern));
EXPECT_TRUE(notification.silent());
}
TEST_F(PlatformNotificationServiceTest, DisplayPersistentPropertiesMatch) {
std::vector<int> vibration_pattern(std::begin(kNotificationVibrationPattern),
std::end(kNotificationVibrationPattern));
PlatformNotificationData data;
data.title = u"My notification's title";
data.body = u"Hello, world!";
data.vibration_pattern = vibration_pattern;
data.silent = true;
data.actions.resize(2);
data.actions[0] = blink::mojom::NotificationAction::New();
data.actions[0]->type = blink::mojom::NotificationActionType::BUTTON;
data.actions[0]->title = u"Button 1";
data.actions[1] = blink::mojom::NotificationAction::New();
data.actions[1]->type = blink::mojom::NotificationActionType::TEXT;
data.actions[1]->title = u"Button 2";
NotificationResources notification_resources;
notification_resources.action_icons.resize(data.actions.size());
EXPECT_CALL(*mock_logger_, LogPersistentNotificationShown());
service()->DisplayPersistentNotification(
kNotificationId, GURL() /* service_worker_scope */,
GURL("https://chrome.com/"), data, notification_resources);
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
Notification notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_PERSISTENT);
EXPECT_EQ("https://chrome.com/", notification.origin_url().spec());
EXPECT_EQ("My notification's title", base::UTF16ToUTF8(notification.title()));
EXPECT_EQ("Hello, world!", base::UTF16ToUTF8(notification.message()));
EXPECT_THAT(notification.vibration_pattern(),
testing::ElementsAreArray(kNotificationVibrationPattern));
EXPECT_TRUE(notification.silent());
const auto& buttons = notification.buttons();
ASSERT_EQ(2u, buttons.size());
EXPECT_EQ("Button 1", base::UTF16ToUTF8(buttons[0].title));
EXPECT_FALSE(buttons[0].placeholder);
EXPECT_EQ("Button 2", base::UTF16ToUTF8(buttons[1].title));
EXPECT_TRUE(buttons[1].placeholder);
}
TEST_F(PlatformNotificationServiceTest, RecordNotificationUkmEvent) {
NotificationDatabaseData data;
data.notification_id = "notification1";
data.origin = GURL("https://example.com");
data.closed_reason = NotificationDatabaseData::ClosedReason::USER;
data.replaced_existing_notification = true;
data.notification_data.icon = GURL("https://icon.com");
data.notification_data.image = GURL("https://image.com");
data.notification_data.renotify = false;
data.notification_data.tag = "tag";
data.notification_data.silent = true;
auto action1 = blink::mojom::NotificationAction::New();
data.notification_data.actions.push_back(std::move(action1));
auto action2 = blink::mojom::NotificationAction::New();
data.notification_data.actions.push_back(std::move(action2));
auto action3 = blink::mojom::NotificationAction::New();
data.notification_data.actions.push_back(std::move(action3));
data.notification_data.require_interaction = false;
data.num_clicks = 3;
data.num_action_button_clicks = 1;
data.time_until_close_millis = base::Milliseconds(10000);
data.time_until_first_click_millis = base::Milliseconds(2222);
data.time_until_last_click_millis = base::Milliseconds(3333);
// Set up UKM recording conditions.
auto* history_service = HistoryServiceFactory::GetForProfile(
profile_.get(), ServiceAccessType::EXPLICIT_ACCESS);
history_service->AddPage(data.origin, base::Time::Now(),
history::SOURCE_BROWSED);
// Initially there are no UKM entries.
std::vector<raw_ptr<const ukm::mojom::UkmEntry, VectorExperimental>> entries =
recorder_->GetEntriesByName(ukm::builders::Notification::kEntryName);
EXPECT_EQ(0u, entries.size());
{
base::RunLoop run_loop;
service()->set_ukm_recorded_closure_for_testing(run_loop.QuitClosure());
service()->RecordNotificationUkmEvent(data);
run_loop.Run();
}
entries =
recorder_->GetEntriesByName(ukm::builders::Notification::kEntryName);
ASSERT_EQ(1u, entries.size());
auto* entry = entries[0].get();
recorder_->ExpectEntryMetric(
entry, kClosedReason,
static_cast<int>(NotificationDatabaseData::ClosedReason::USER));
recorder_->ExpectEntryMetric(entry, kDidReplaceAnotherNotification, true);
recorder_->ExpectEntryMetric(entry, kHasBadge, false);
recorder_->ExpectEntryMetric(entry, kHasIcon, 1);
recorder_->ExpectEntryMetric(entry, kHasImage, 1);
recorder_->ExpectEntryMetric(entry, kHasRenotify, false);
recorder_->ExpectEntryMetric(entry, kHasTag, true);
recorder_->ExpectEntryMetric(entry, kIsSilent, 1);
recorder_->ExpectEntryMetric(entry, kNumActions, 3);
recorder_->ExpectEntryMetric(entry, kNumActionButtonClicks, 1);
recorder_->ExpectEntryMetric(entry, kNumClicks, 3);
recorder_->ExpectEntryMetric(entry, kRequireInteraction, false);
recorder_->ExpectEntryMetric(entry, kTimeUntilCloseMillis, 10000);
recorder_->ExpectEntryMetric(entry, kTimeUntilFirstClickMillis, 2222);
recorder_->ExpectEntryMetric(entry, kTimeUntilLastClickMillis, 3333);
}
// Expect each call to ReadNextPersistentNotificationId to return a larger
// value.
TEST_F(PlatformNotificationServiceTest, NextPersistentNotificationId) {
int64_t first_id = service()->ReadNextPersistentNotificationId();
int64_t second_id = service()->ReadNextPersistentNotificationId();
EXPECT_LT(first_id, second_id);
}
TEST_F(PlatformNotificationServiceTest,
ProposedDisruptiveNotificationRevocationMetricsPersistent) {
GURL url("https://chrome.test/");
const int kDailyNotificationCount = 4;
HostContentSettingsMap* hcsm =
HostContentSettingsMapFactory::GetForProfile(profile_.get());
DisruptiveNotificationPermissionsManager::RevocationEntry entry(
/*revocation_state=*/DisruptiveNotificationPermissionsManager::
RevocationState::kProposed,
/*site_engagement=*/0.0,
/*daily_notification_count=*/kDailyNotificationCount);
DisruptiveNotificationPermissionsManager::ContentSettingHelper(*hcsm)
.PersistRevocationEntry(url, entry);
PlatformNotificationData data;
data.title = u"My notification's title";
data.body = u"Hello, world!";
service()->DisplayPersistentNotification(kNotificationId,
GURL() /* service_worker_scope */,
url, data, NotificationResources());
EXPECT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
// Check that the correct metric is reported.
EXPECT_EQ(1u, recorder_
->GetEntriesByName(
"SafetyHub.DisruptiveNotificationRevocations.Proposed")
.size());
}
TEST_F(PlatformNotificationServiceTest,
ProposedDisruptiveNotificationRevocationMetricsNonPersistent) {
GURL url("https://chrome.test/");
const int kDailyNotificationCount = 4;
HostContentSettingsMap* hcsm =
HostContentSettingsMapFactory::GetForProfile(profile_.get());
DisruptiveNotificationPermissionsManager::RevocationEntry entry(
/*revocation_state=*/DisruptiveNotificationPermissionsManager::
RevocationState::kProposed,
/*site_engagement=*/0.0,
/*daily_notification_count=*/kDailyNotificationCount);
DisruptiveNotificationPermissionsManager::ContentSettingHelper(*hcsm)
.PersistRevocationEntry(url, entry);
PlatformNotificationData data;
data.title = u"My notification's title";
data.body = u"Hello, world!";
service()->DisplayNotification(kNotificationId, url,
/*document_url=*/GURL(), data,
NotificationResources());
EXPECT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
// Check that the correct metric is reported.
EXPECT_EQ(1u, recorder_
->GetEntriesByName(
"SafetyHub.DisruptiveNotificationRevocations.Proposed")
.size());
}
#if !BUILDFLAG(IS_ANDROID)
class PlatformNotificationServiceTest_WebApps
: public PlatformNotificationServiceTest {
public:
void SetUp() override {
PlatformNotificationServiceTest::SetUp();
web_app::test::AwaitStartWebAppProviderAndSubsystems(profile_.get());
web_app::WebAppProvider* provider =
web_app::WebAppProvider::GetForTest(profile_.get());
std::unique_ptr<web_app::WebAppInstallInfo> web_app =
web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(
kWebAppStartUrl);
web_app->title = u"Test app 1";
installed_app_id =
web_app::test::InstallWebApp(profile_.get(), std::move(web_app));
web_app = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(
kInstalledNestedWebAppStartUrl);
web_app->title = u"Test app 2";
nested_installed_app_id =
web_app::test::InstallWebApp(profile_.get(), std::move(web_app));
web_app = web_app::WebAppInstallInfo::CreateWithStartUrlForTesting(
kNotInstalledNestedWebAppStartUrl);
web_app->title = u"Test app 3";
web_app::WebAppInstallParams params;
params.install_state =
web_app::proto::InstallState::SUGGESTED_FROM_ANOTHER_DEVICE;
// OS Hooks must be disabled for non-locally installed app.
params.add_to_applications_menu = false;
params.add_to_desktop = false;
params.add_to_quick_launch_bar = false;
base::test::TestFuture<const webapps::AppId&, webapps::InstallResultCode>
future;
provider->scheduler().InstallFromInfoWithParams(
std::move(web_app), /*overwrite_existing_manifest_fields=*/false,
webapps::WebappInstallSource::OMNIBOX_INSTALL_ICON,
future.GetCallback(), params);
ASSERT_TRUE(future.Wait());
EXPECT_EQ(future.Get<webapps::InstallResultCode>(),
webapps::InstallResultCode::kSuccessNewInstall);
}
protected:
const GURL kWebAppOrigin = GURL("https://example.com");
const GURL kWebAppStartUrl = GURL("https://example.com/scope/start.html");
const GURL kInstalledNestedWebAppStartUrl =
GURL("https://example.com/scope/other/foo.html");
const GURL kNotInstalledNestedWebAppStartUrl =
GURL("https://example.com/scope/nestedscope/foo.html");
const GURL kOutOfScopeUrl = GURL("https://example.com/outofscope");
const GURL kServiceWorkerScope = GURL("https://example.com/scope/");
static PlatformNotificationData CreateDummyNotificationData() {
PlatformNotificationData data;
data.title = u"My Notification";
data.body = u"Hello, world!";
return data;
}
const PlatformNotificationData kNotificationData =
CreateDummyNotificationData();
webapps::AppId installed_app_id;
webapps::AppId nested_installed_app_id;
private:
web_app::OsIntegrationTestOverrideBlockingRegistration fake_os_integration_;
};
TEST_F(PlatformNotificationServiceTest_WebApps, IncomingCallWebApp) {
EXPECT_TRUE(service()->IsActivelyInstalledWebAppScope(kWebAppStartUrl));
web_app::test::UninstallAllWebApps(profile_.get());
EXPECT_FALSE(service()->IsActivelyInstalledWebAppScope(kWebAppStartUrl));
}
TEST_F(PlatformNotificationServiceTest_WebApps, CreateNotificationFromData) {
PlatformNotificationData notification_data;
notification_data.title = u"My Notification";
notification_data.body = u"Hello, world!";
GURL origin = url::Origin::Create(kWebAppStartUrl).GetURL();
Notification notification = service()->CreateNotificationFromData(
origin, "id", notification_data, NotificationResources(),
/*web_app_hint_url=*/kWebAppStartUrl);
EXPECT_EQ(notification.notifier_id().web_app_id, installed_app_id);
Notification nested_notification = service()->CreateNotificationFromData(
origin, "id", notification_data, NotificationResources(),
/*web_app_hint_url=*/kInstalledNestedWebAppStartUrl);
EXPECT_EQ(nested_notification.notifier_id().web_app_id,
nested_installed_app_id);
}
TEST_F(PlatformNotificationServiceTest_WebApps, PopulateWebAppId_MatchesScope) {
service()->DisplayNotification(kNotificationId, kWebAppOrigin,
kNotInstalledNestedWebAppStartUrl,
kNotificationData, NotificationResources());
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
Notification notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_NON_PERSISTENT);
EXPECT_EQ(installed_app_id, notification.notifier_id().web_app_id);
service()->DisplayPersistentNotification(kNotificationId, kServiceWorkerScope,
kWebAppOrigin, kNotificationData,
NotificationResources());
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_PERSISTENT);
EXPECT_EQ(installed_app_id, notification.notifier_id().web_app_id);
}
TEST_F(PlatformNotificationServiceTest_WebApps,
PopulateWebAppId_InNestedScope) {
service()->DisplayNotification(kNotificationId, kWebAppOrigin,
kInstalledNestedWebAppStartUrl,
kNotificationData, NotificationResources());
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
Notification notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_NON_PERSISTENT);
EXPECT_EQ(nested_installed_app_id, notification.notifier_id().web_app_id);
}
TEST_F(PlatformNotificationServiceTest_WebApps, PopulateWebAppId_NotInScope) {
service()->DisplayNotification(kNotificationId, kWebAppOrigin, kOutOfScopeUrl,
kNotificationData, NotificationResources());
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
Notification notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_NON_PERSISTENT);
EXPECT_EQ(std::nullopt, notification.notifier_id().web_app_id);
service()->DisplayPersistentNotification(kNotificationId, kOutOfScopeUrl,
kWebAppOrigin, kNotificationData,
NotificationResources());
ASSERT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
notification = GetDisplayedNotificationForType(
NotificationHandler::Type::WEB_PERSISTENT);
EXPECT_EQ(std::nullopt, notification.notifier_id().web_app_id);
}
#endif // !BUILDFLAG(IS_ANDROID)
#if BUILDFLAG(ENABLE_EXTENSIONS)
TEST_F(PlatformNotificationServiceTest, DisplayNameForContextMessage) {
std::u16string display_name =
service()->DisplayNameForContextMessage(GURL("https://chrome.com/"));
EXPECT_TRUE(display_name.empty());
// Create a mocked extension.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder()
.SetID("honijodknafkokifofgiaalefdiedpko")
.SetManifest(base::Value::Dict()
.Set("name", "NotificationTest")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("description", "Test Extension"))
.Build();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(profile_.get());
EXPECT_TRUE(registry->AddEnabled(extension));
display_name = service()->DisplayNameForContextMessage(
GURL("chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html"));
EXPECT_EQ("NotificationTest", base::UTF16ToUTF8(display_name));
}
TEST_F(PlatformNotificationServiceTest, CreateNotificationFromData) {
PlatformNotificationData notification_data;
notification_data.title = u"My Notification";
notification_data.body = u"Hello, world!";
GURL origin("https://chrome.com/");
Notification notification = service()->CreateNotificationFromData(
origin, "id", notification_data, NotificationResources(),
/*web_app_hint_url=*/GURL());
EXPECT_TRUE(notification.context_message().empty());
// Create a mocked extension.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder()
.SetID("honijodknafkokifofgiaalefdiedpko")
.SetManifest(base::Value::Dict()
.Set("name", "NotificationTest")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("description", "Test Extension"))
.Build();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(profile_.get());
EXPECT_TRUE(registry->AddEnabled(extension));
notification = service()->CreateNotificationFromData(
GURL("chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html"),
"id", notification_data, NotificationResources(),
/*web_app_hint_url=*/GURL());
EXPECT_EQ("NotificationTest",
base::UTF16ToUTF8(notification.context_message()));
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
#if BUILDFLAG(IS_CHROMEOS)
using PlatformNotificationServiceTest_WebAppNotificationIconAndTitle =
PlatformNotificationServiceTest;
TEST_F(PlatformNotificationServiceTest_WebAppNotificationIconAndTitle,
FindWebAppIconAndTitle_NoApp) {
web_app::FakeWebAppProvider* provider =
web_app::FakeWebAppProvider::Get(profile_.get());
provider->Start();
const GURL web_app_url{"https://example.org/"};
EXPECT_FALSE(service()->FindWebAppIconAndTitle(web_app_url).has_value());
}
TEST_F(PlatformNotificationServiceTest_WebAppNotificationIconAndTitle,
FindWebAppIconAndTitle) {
web_app::FakeWebAppProvider* provider =
web_app::FakeWebAppProvider::Get(profile_.get());
web_app::WebAppIconManager& icon_manager = provider->GetIconManager();
std::unique_ptr<web_app::WebApp> web_app = web_app::test::CreateWebApp();
const GURL web_app_url = web_app->start_url();
const webapps::AppId app_id = web_app->app_id();
web_app->SetName("Web App Title");
IconManagerWriteGeneratedIcons(icon_manager, app_id,
{{web_app::IconPurpose::MONOCHROME,
{web_app::icon_size::k16},
{SK_ColorTRANSPARENT}}});
web_app->SetDownloadedIconSizes(web_app::IconPurpose::MONOCHROME,
{web_app::icon_size::k16});
provider->GetRegistrarMutable().registry().emplace(app_id,
std::move(web_app));
base::RunLoop run_loop;
icon_manager.SetFaviconMonochromeReadCallbackForTesting(
base::BindLambdaForTesting(
[&](const webapps::AppId& cached_app_id) { run_loop.Quit(); }));
icon_manager.Start();
run_loop.Run();
provider->Start();
std::optional<PlatformNotificationServiceImpl::WebAppIconAndTitle>
icon_and_title = service()->FindWebAppIconAndTitle(web_app_url);
ASSERT_TRUE(icon_and_title.has_value());
EXPECT_EQ(u"Web App Title", icon_and_title->title);
EXPECT_FALSE(icon_and_title->icon.isNull());
EXPECT_EQ(
SK_ColorTRANSPARENT,
icon_and_title->icon.GetRepresentation(1.0f).GetBitmap().getColor(0, 0));
}
#endif // BUILDFLAG(IS_CHROMEOS)
class PlatformNotificationServiceTest_NotificationContentDetection
: public PlatformNotificationServiceTest,
public testing::WithParamInterface<bool> {
public:
void SetUp() override {
TestingProfile::Builder profile_builder;
profile_builder.AddTestingFactory(
HistoryServiceFactory::GetInstance(),
HistoryServiceFactory::GetDefaultFactory());
profile_ = profile_builder.Build();
if (IsSafeBrowsingEnabled()) {
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kSafeBrowsingEnabled, std::make_unique<base::Value>(true));
} else {
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kSafeBrowsingEnabled, std::make_unique<base::Value>(false));
}
mock_notification_content_detection_service_ = static_cast<
safe_browsing::MockNotificationContentDetectionService*>(
safe_browsing::NotificationContentDetectionServiceFactory::GetInstance()
->SetTestingFactoryAndUse(
profile_.get(),
base::BindRepeating(
&safe_browsing::MockNotificationContentDetectionService::
FactoryForTests,
&model_observer_tracker_,
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock()}))));
}
void TearDown() override {
mock_notification_content_detection_service_ = nullptr;
PlatformNotificationServiceTest::TearDown();
}
bool IsSafeBrowsingEnabled() { return GetParam(); }
protected:
raw_ptr<safe_browsing::MockNotificationContentDetectionService>
mock_notification_content_detection_service_ = nullptr;
safe_browsing::TestModelObserverTracker model_observer_tracker_;
};
INSTANTIATE_TEST_SUITE_P(
,
PlatformNotificationServiceTest_NotificationContentDetection,
testing::Bool());
TEST_P(PlatformNotificationServiceTest_NotificationContentDetection,
PerformNotificationContentDetectionWhenEnabled) {
PlatformNotificationData data;
data.title = u"My notification's title";
data.body = u"Hello, world!";
int expected_number_of_calls = 0;
if (IsSafeBrowsingEnabled()) {
expected_number_of_calls = 1;
}
EXPECT_CALL(*mock_notification_content_detection_service_,
MaybeCheckNotificationContentDetectionModel(_, _, _, _))
.Times(expected_number_of_calls);
service()->DisplayPersistentNotification(
kNotificationId, GURL() /* service_worker_scope */,
GURL("https://chrome.com/"), data, NotificationResources());
}
class PlatformNotificationServiceTest_ReportNotificationContentDetectionData
: public PlatformNotificationServiceTest {
public:
void SetUp() override {
TestingProfile::Builder profile_builder;
profile_builder.AddTestingFactory(
HistoryServiceFactory::GetInstance(),
HistoryServiceFactory::GetDefaultFactory());
profile_ = profile_builder.Build();
scoped_feature_list_.InitWithFeatures(
{safe_browsing::kReportNotificationContentDetectionData}, {});
profile_->GetTestingPrefService()->SetManagedPref(
prefs::kSafeBrowsingEnabled, std::make_unique<base::Value>(true));
mock_notification_content_detection_service_ = static_cast<
safe_browsing::MockNotificationContentDetectionService*>(
safe_browsing::NotificationContentDetectionServiceFactory::GetInstance()
->SetTestingFactoryAndUse(
profile_.get(),
base::BindRepeating(
&safe_browsing::MockNotificationContentDetectionService::
FactoryForTests,
&model_observer_tracker_,
base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock()}))));
}
void TearDown() override {
mock_notification_content_detection_service_ = nullptr;
PlatformNotificationServiceTest::TearDown();
}
protected:
raw_ptr<safe_browsing::MockNotificationContentDetectionService>
mock_notification_content_detection_service_ = nullptr;
safe_browsing::TestModelObserverTracker model_observer_tracker_;
};
TEST_F(PlatformNotificationServiceTest_ReportNotificationContentDetectionData,
UpdateNotificationDatabaseMetadata) {
// Store notification data in `NotificationDatabase`.
const int64_t kFakeServiceWorkerRegistrationId = 42;
int notification_id = 1;
GURL origin("https://example.com");
NotificationDatabaseData notification_database_data;
notification_database_data.origin = origin;
GetPlatformNotificationContext(origin)->WriteNotificationData(
notification_id, kFakeServiceWorkerRegistrationId, origin,
notification_database_data, base::DoNothing());
base::RunLoop().RunUntilIdle();
// Update `NotificationDatabase` entry with metadata.
std::string full_notification_id_str =
"p#" + origin.spec() + "#0" + base::NumberToString(notification_id);
Notification notification = message_center::Notification(
message_center::NOTIFICATION_TYPE_SIMPLE, full_notification_id_str,
/*title=*/std::u16string(),
/*message=*/std::u16string(), /*icon=*/ui::ImageModel(),
/*display_source=*/std::u16string(), origin, message_center::NotifierId(),
message_center::RichNotificationData(), /*delegate=*/nullptr);
auto metadata = std::make_unique<PersistentNotificationMetadata>();
bool is_on_global_cache_list = false;
bool is_allowlisted_by_user = false;
double suspicious_score = 70.0;
service()->UpdatePersistentMetadataThenDisplay(
notification, std::move(metadata), /*should_show_warning=*/true,
safe_browsing::NotificationContentDetectionModel::GetSerializedMetadata(
is_on_global_cache_list, is_allowlisted_by_user, suspicious_score));
// Read and check the entry from `NotificationDatabase`.
NotificationDatabaseData data =
ReadNotificationDataAndRecordInteractionSynchronous(
GetPlatformNotificationContext(origin),
base::NumberToString(notification_id), origin);
ASSERT_TRUE(data.serialized_metadata.contains(
safe_browsing::kNotificationContentDetectionMetadataDictionaryKey));
ASSERT_EQ(
safe_browsing::NotificationContentDetectionModel::GetSerializedMetadata(
is_on_global_cache_list, is_allowlisted_by_user, suspicious_score),
data.serialized_metadata.at(
safe_browsing::kNotificationContentDetectionMetadataDictionaryKey));
HostContentSettingsMap* hcsm =
HostContentSettingsMapFactory::GetForProfile(profile_.get());
base::Value cur_value(hcsm->GetWebsiteSetting(
origin, origin, ContentSettingsType::SUSPICIOUS_NOTIFICATION_IDS));
#if BUILDFLAG(IS_ANDROID)
const base::Value::List* suspicious_notification_ids =
cur_value.GetDict().FindList(
safe_browsing::kSuspiciousNotificationIdsKey);
ASSERT_EQ(1u, suspicious_notification_ids->size());
ASSERT_EQ(full_notification_id_str,
suspicious_notification_ids->front().GetString());
#else
ASSERT_TRUE(cur_value.is_none());
#endif
}