|  | // Copyright 2020 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/notifications/notification_display_service_impl.h" | 
|  |  | 
|  | #include <memory> | 
|  | #include <optional> | 
|  | #include <set> | 
|  | #include <string> | 
|  | #include <vector> | 
|  |  | 
|  | #include "base/functional/callback_helpers.h" | 
|  | #include "base/memory/raw_ptr.h" | 
|  | #include "base/memory/scoped_refptr.h" | 
|  | #include "base/run_loop.h" | 
|  | #include "base/test/bind.h" | 
|  | #include "base/test/scoped_feature_list.h" | 
|  | #include "base/test/test_future.h" | 
|  | #include "build/build_config.h" | 
|  | #include "chrome/browser/browser_features.h" | 
|  | #include "chrome/browser/notifications/notification_blocker.h" | 
|  | #include "chrome/browser/notifications/notification_display_queue.h" | 
|  | #include "chrome/browser/notifications/notification_platform_bridge_delegator.h" | 
|  | #include "chrome/common/notifications/notification_operation.h" | 
|  | #include "chrome/test/base/testing_profile.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/test/browser_task_environment.h" | 
|  | #include "content/public/test/test_web_contents_factory.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "ui/message_center/message_center.h" | 
|  | #include "ui/message_center/public/cpp/notification.h" | 
|  | #include "ui/message_center/public/cpp/notification_delegate.h" | 
|  | #include "ui/message_center/public/cpp/notification_types.h" | 
|  | #include "url/gurl.h" | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | #include "chrome/browser/nearby_sharing/nearby_sharing_service_factory.h" | 
|  | #endif | 
|  |  | 
|  | #if !BUILDFLAG(IS_ANDROID) | 
|  | #include "chrome/browser/notifications/muted_notification_handler.h" | 
|  | #include "chrome/browser/notifications/screen_capture_notification_blocker.h" | 
|  | #endif | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class FakeNotificationBlocker : public NotificationBlocker { | 
|  | public: | 
|  | FakeNotificationBlocker() = default; | 
|  | ~FakeNotificationBlocker() override = default; | 
|  |  | 
|  | // NotificationDisplayQueue::NotificationBlocker: | 
|  | bool ShouldBlockNotification( | 
|  | const message_center::Notification& notification) override { | 
|  | return should_block_; | 
|  | } | 
|  |  | 
|  | void SetShouldBlockNotifications(bool should_block) { | 
|  | should_block_ = should_block; | 
|  | NotifyBlockingStateChanged(); | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool should_block_ = false; | 
|  | }; | 
|  |  | 
|  | class TestNotificationPlatformBridgeDelegator | 
|  | : public NotificationPlatformBridgeDelegator { | 
|  | public: | 
|  | explicit TestNotificationPlatformBridgeDelegator(Profile* profile) | 
|  | : NotificationPlatformBridgeDelegator(profile, base::DoNothing()) {} | 
|  | ~TestNotificationPlatformBridgeDelegator() override = default; | 
|  |  | 
|  | // NotificationPlatformBridgeDelegator: | 
|  | void Display( | 
|  | NotificationHandler::Type notification_type, | 
|  | const message_center::Notification& notification, | 
|  | std::unique_ptr<NotificationCommon::Metadata> metadata) override { | 
|  | notification_ids_.insert(notification.id()); | 
|  | notification_origins_[notification.id()] = notification.origin_url(); | 
|  | } | 
|  |  | 
|  | void Close(NotificationHandler::Type notification_type, | 
|  | const std::string& notification_id) override { | 
|  | notification_ids_.erase(notification_id); | 
|  | notification_origins_.erase(notification_id); | 
|  | } | 
|  |  | 
|  | void GetDisplayed(GetDisplayedNotificationsCallback callback) const override { | 
|  | std::move(callback).Run(notification_ids_, /*supports_sync=*/true); | 
|  | } | 
|  |  | 
|  | void GetDisplayedForOrigin( | 
|  | const GURL& origin, | 
|  | GetDisplayedNotificationsCallback callback) const override { | 
|  | std::set<std::string> result; | 
|  | for (const auto& id : notification_ids_) { | 
|  | auto origin_it = notification_origins_.find(id); | 
|  | if (url::IsSameOriginWith(origin_it->second, origin)) { | 
|  | result.insert(id); | 
|  | } | 
|  | } | 
|  | std::move(callback).Run(result, /*supports_sync=*/true); | 
|  | } | 
|  |  | 
|  | private: | 
|  | std::set<std::string> notification_ids_; | 
|  | std::map<std::string, GURL> notification_origins_; | 
|  | }; | 
|  |  | 
|  | message_center::Notification CreateNotification(const std::string& id, | 
|  | const GURL& origin = {}) { | 
|  | return message_center::Notification( | 
|  | message_center::NOTIFICATION_TYPE_SIMPLE, id, /*title=*/std::u16string(), | 
|  | /*message=*/std::u16string(), /*icon=*/ui::ImageModel(), | 
|  | /*display_source=*/std::u16string(), origin, message_center::NotifierId(), | 
|  | message_center::RichNotificationData(), /*delegate=*/nullptr); | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class BaseNotificationDisplayServiceImplTest : public testing::Test { | 
|  | protected: | 
|  | BaseNotificationDisplayServiceImplTest() = default; | 
|  | ~BaseNotificationDisplayServiceImplTest() override = default; | 
|  |  | 
|  | // testing::Test: | 
|  | void SetUp() override { | 
|  | service_ = std::make_unique<NotificationDisplayServiceImpl>(&profile_); | 
|  |  | 
|  | auto notification_delegator = | 
|  | std::make_unique<TestNotificationPlatformBridgeDelegator>(&profile_); | 
|  | notification_delegator_ = notification_delegator.get(); | 
|  |  | 
|  | service_->SetNotificationPlatformBridgeDelegatorForTesting( | 
|  | std::move(notification_delegator)); | 
|  | } | 
|  |  | 
|  | Profile* profile() { return &profile_; } | 
|  |  | 
|  | NotificationDisplayServiceImpl& service() { return *service_; } | 
|  |  | 
|  | protected: | 
|  | std::set<std::string> GetDisplayedServiceSync() { | 
|  | base::test::TestFuture<std::set<std::string>, bool> displayed; | 
|  | service_->GetDisplayed(displayed.GetCallback()); | 
|  | return displayed.Get<0>(); | 
|  | } | 
|  |  | 
|  | std::set<std::string> GetDisplayedForOriginServiceSync(const GURL& origin) { | 
|  | base::test::TestFuture<std::set<std::string>, bool> displayed; | 
|  | service_->GetDisplayedForOrigin(origin, displayed.GetCallback()); | 
|  | return displayed.Get<0>(); | 
|  | } | 
|  |  | 
|  | std::set<std::string> GetDisplayedPlatformSync() { | 
|  | base::test::TestFuture<std::set<std::string>, bool> displayed; | 
|  | notification_delegator_->GetDisplayed(displayed.GetCallback()); | 
|  | return displayed.Get<0>(); | 
|  | } | 
|  |  | 
|  | std::set<std::string> GetDisplayedForOriginPlatformSync(const GURL& origin) { | 
|  | base::test::TestFuture<std::set<std::string>, bool> displayed; | 
|  | notification_delegator_->GetDisplayedForOrigin(origin, | 
|  | displayed.GetCallback()); | 
|  | return displayed.Get<0>(); | 
|  | } | 
|  |  | 
|  | void DisplayNotification(const std::string id, const GURL& origin = {}) { | 
|  | service_->Display(NotificationHandler::Type::WEB_PERSISTENT, | 
|  | CreateNotification(id, origin), /*metadata=*/nullptr); | 
|  | } | 
|  |  | 
|  | void CloseNotification(const std::string id) { | 
|  | service_->Close(NotificationHandler::Type::WEB_PERSISTENT, id); | 
|  | } | 
|  |  | 
|  | private: | 
|  | content::BrowserTaskEnvironment task_environment_; | 
|  | TestingProfile profile_; | 
|  | std::unique_ptr<NotificationDisplayServiceImpl> service_; | 
|  | raw_ptr<TestNotificationPlatformBridgeDelegator> notification_delegator_ = | 
|  | nullptr; | 
|  | }; | 
|  |  | 
|  | // Test class that uses a FakeNotificationBlocker instead of the real ones. | 
|  | class NotificationDisplayServiceImplTest | 
|  | : public BaseNotificationDisplayServiceImplTest { | 
|  | protected: | 
|  | NotificationDisplayServiceImplTest() = default; | 
|  | ~NotificationDisplayServiceImplTest() override = default; | 
|  |  | 
|  | // BaseNotificationDisplayServiceImplTest: | 
|  | void SetUp() override { | 
|  | BaseNotificationDisplayServiceImplTest::SetUp(); | 
|  |  | 
|  | auto blocker = std::make_unique<FakeNotificationBlocker>(); | 
|  | notification_blocker_ = blocker.get(); | 
|  |  | 
|  | NotificationDisplayQueue::NotificationBlockers blockers; | 
|  | blockers.push_back(std::move(blocker)); | 
|  | service().SetBlockersForTesting(std::move(blockers)); | 
|  | } | 
|  |  | 
|  | FakeNotificationBlocker& notification_blocker() { | 
|  | return *notification_blocker_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | raw_ptr<FakeNotificationBlocker, DanglingUntriaged> notification_blocker_ = | 
|  | nullptr; | 
|  | }; | 
|  |  | 
|  | TEST_F(NotificationDisplayServiceImplTest, DisplayWithoutBlockers) { | 
|  | service().SetBlockersForTesting({}); | 
|  | std::string notification_id = "id"; | 
|  | GURL origin("https://example.com/"); | 
|  | DisplayNotification(notification_id, origin); | 
|  |  | 
|  | std::set<std::string> displayed = GetDisplayedServiceSync(); | 
|  | EXPECT_EQ(1u, displayed.size()); | 
|  | EXPECT_EQ(1u, displayed.count(notification_id)); | 
|  | EXPECT_EQ(displayed, GetDisplayedPlatformSync()); | 
|  | EXPECT_EQ(displayed, GetDisplayedForOriginServiceSync(origin)); | 
|  | EXPECT_EQ(displayed, GetDisplayedForOriginPlatformSync(origin)); | 
|  | EXPECT_TRUE( | 
|  | GetDisplayedForOriginServiceSync(GURL("https://foo.bar")).empty()); | 
|  | EXPECT_TRUE( | 
|  | GetDisplayedForOriginPlatformSync(GURL("https://foo.bar")).empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(NotificationDisplayServiceImplTest, DisplayWithAllowingBlocker) { | 
|  | std::string notification_id = "id"; | 
|  | DisplayNotification(notification_id); | 
|  |  | 
|  | std::set<std::string> displayed = GetDisplayedServiceSync(); | 
|  | EXPECT_EQ(1u, displayed.size()); | 
|  | EXPECT_EQ(1u, displayed.count(notification_id)); | 
|  | EXPECT_EQ(displayed, GetDisplayedPlatformSync()); | 
|  | } | 
|  |  | 
|  | TEST_F(NotificationDisplayServiceImplTest, DisplayWithBlockingBlocker) { | 
|  | notification_blocker().SetShouldBlockNotifications(true); | 
|  | std::string notification_id = "id"; | 
|  | GURL origin("https://example.com/"); | 
|  | DisplayNotification(notification_id, origin); | 
|  |  | 
|  | std::set<std::string> displayed = GetDisplayedServiceSync(); | 
|  | EXPECT_EQ(1u, displayed.size()); | 
|  | EXPECT_EQ(1u, displayed.count(notification_id)); | 
|  | EXPECT_TRUE(GetDisplayedPlatformSync().empty()); | 
|  | EXPECT_EQ(displayed, GetDisplayedForOriginServiceSync(origin)); | 
|  | EXPECT_TRUE(GetDisplayedForOriginPlatformSync(origin).empty()); | 
|  | } | 
|  |  | 
|  | TEST_F(NotificationDisplayServiceImplTest, UnblockQueuedNotification) { | 
|  | notification_blocker().SetShouldBlockNotifications(true); | 
|  | std::string notification_id = "id"; | 
|  | DisplayNotification(notification_id); | 
|  | EXPECT_TRUE(GetDisplayedPlatformSync().empty()); | 
|  |  | 
|  | notification_blocker().SetShouldBlockNotifications(false); | 
|  | std::set<std::string> displayed = GetDisplayedServiceSync(); | 
|  | EXPECT_EQ(1u, displayed.size()); | 
|  | EXPECT_EQ(1u, displayed.count(notification_id)); | 
|  | EXPECT_EQ(displayed, GetDisplayedPlatformSync()); | 
|  | } | 
|  |  | 
|  | TEST_F(NotificationDisplayServiceImplTest, CloseQueuedNotification) { | 
|  | notification_blocker().SetShouldBlockNotifications(true); | 
|  | std::string notification_id = "id"; | 
|  | DisplayNotification(notification_id); | 
|  | EXPECT_EQ(1u, GetDisplayedServiceSync().size()); | 
|  | EXPECT_TRUE(GetDisplayedPlatformSync().empty()); | 
|  |  | 
|  | CloseNotification(notification_id); | 
|  | EXPECT_TRUE(GetDisplayedServiceSync().empty()); | 
|  | EXPECT_TRUE(GetDisplayedPlatformSync().empty()); | 
|  |  | 
|  | notification_blocker().SetShouldBlockNotifications(false); | 
|  | EXPECT_TRUE(GetDisplayedServiceSync().empty()); | 
|  | EXPECT_TRUE(GetDisplayedPlatformSync().empty()); | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_CHROMEOS) | 
|  | TEST_F(NotificationDisplayServiceImplTest, NearbyNotificationHandler) { | 
|  | // Add the Nearby Share handler if and only if Nearby Share is supported. | 
|  | { | 
|  | NearbySharingServiceFactory:: | 
|  | SetIsNearbyShareSupportedForBrowserContextForTesting(false); | 
|  | NotificationDisplayServiceImpl service(profile()); | 
|  | EXPECT_FALSE(service.GetNotificationHandler( | 
|  | NotificationHandler::Type::NEARBY_SHARE)); | 
|  | } | 
|  | { | 
|  | NearbySharingServiceFactory:: | 
|  | SetIsNearbyShareSupportedForBrowserContextForTesting(true); | 
|  | NotificationDisplayServiceImpl service(profile()); | 
|  | EXPECT_TRUE(service.GetNotificationHandler( | 
|  | NotificationHandler::Type::NEARBY_SHARE)); | 
|  | } | 
|  | } | 
|  | #endif | 
|  |  | 
|  | #if !BUILDFLAG(IS_ANDROID) | 
|  |  | 
|  | // Desktop specific test class that uses the default NotificationBlockers. | 
|  | class DesktopNotificationDisplayServiceImplTest | 
|  | : public BaseNotificationDisplayServiceImplTest { | 
|  | protected: | 
|  | DesktopNotificationDisplayServiceImplTest() = default; | 
|  | ~DesktopNotificationDisplayServiceImplTest() override = default; | 
|  |  | 
|  | // BaseNotificationDisplayServiceImplTest: | 
|  | void SetUp() override { | 
|  | BaseNotificationDisplayServiceImplTest::SetUp(); | 
|  |  | 
|  | auto* muted_handler = | 
|  | static_cast<MutedNotificationHandler*>(service().GetNotificationHandler( | 
|  | NotificationHandler::Type::NOTIFICATIONS_MUTED)); | 
|  | ASSERT_TRUE(muted_handler); | 
|  | screen_capture_blocker_ = static_cast<ScreenCaptureNotificationBlocker*>( | 
|  | muted_handler->get_delegate_for_testing()); | 
|  | ASSERT_TRUE(screen_capture_blocker_); | 
|  | } | 
|  |  | 
|  | ScreenCaptureNotificationBlocker* screen_capture_notification_blocker() { | 
|  | return screen_capture_blocker_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | raw_ptr<ScreenCaptureNotificationBlocker> screen_capture_blocker_ = nullptr; | 
|  | }; | 
|  |  | 
|  | TEST_F(DesktopNotificationDisplayServiceImplTest, SnoozeDuringScreenCapture) { | 
|  | base::test::ScopedFeatureList list; | 
|  | list.InitAndEnableFeature(features::kMuteNotificationSnoozeAction); | 
|  |  | 
|  | content::TestWebContentsFactory web_contents_factory; | 
|  | content::WebContents* contents = | 
|  | web_contents_factory.CreateWebContents(profile()); | 
|  | EXPECT_TRUE(GetDisplayedPlatformSync().empty()); | 
|  |  | 
|  | // Start a screen capture session. | 
|  | screen_capture_notification_blocker()->OnIsCapturingDisplayChanged( | 
|  | contents, /*is_capturing_display=*/true); | 
|  |  | 
|  | // Displaying a notification should show the "Notifications Muted" generic | 
|  | // notification instead of the real notification content. | 
|  | std::string notification_id_1 = "id1"; | 
|  | DisplayNotification(notification_id_1); | 
|  | EXPECT_EQ(1u, GetDisplayedPlatformSync().size()); | 
|  | EXPECT_EQ(1u, GetDisplayedPlatformSync().count(kMuteNotificationId)); | 
|  |  | 
|  | // Emulate the user clicking on the "Snooze" action button. | 
|  | base::RunLoop run_loop; | 
|  | service().ProcessNotificationOperation( | 
|  | NotificationOperation::kClick, | 
|  | NotificationHandler::Type::NOTIFICATIONS_MUTED, /*origin=*/GURL(), | 
|  | kMuteNotificationId, /*action_index=*/0, /*reply=*/std::nullopt, | 
|  | /*by_user=*/true, /*is_suspicious=*/false, | 
|  | base::BindOnce([](base::RunLoop* looper) { looper->Quit(); }, &run_loop)); | 
|  | run_loop.Run(); | 
|  |  | 
|  | // Clicking "Snooze" should remove the "Notifications Muted" notification. | 
|  | EXPECT_TRUE(GetDisplayedPlatformSync().empty()); | 
|  |  | 
|  | // Showing another notification should not trigger any visible notification. | 
|  | std::string notification_id_2 = "id2"; | 
|  | DisplayNotification(notification_id_2); | 
|  | EXPECT_TRUE(GetDisplayedPlatformSync().empty()); | 
|  |  | 
|  | // Stopping the screen sharing session should re-display all previously muted | 
|  | // notifications. | 
|  | screen_capture_notification_blocker()->OnIsCapturingDisplayChanged( | 
|  | contents, /*is_capturing_display=*/false); | 
|  | EXPECT_EQ(2u, GetDisplayedPlatformSync().size()); | 
|  | EXPECT_EQ(1u, GetDisplayedPlatformSync().count(notification_id_1)); | 
|  | EXPECT_EQ(1u, GetDisplayedPlatformSync().count(notification_id_2)); | 
|  | } | 
|  |  | 
|  | #endif  // !BUILDFLAG(IS_ANDROID) |