| // 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 <memory> |
| #include <set> |
| #include <string> |
| #include <vector> |
| |
| #include "base/memory/raw_ptr.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "chrome/browser/notifications/notification_blocker.h" |
| #include "chrome/browser/notifications/notification_display_queue.h" |
| #include "chrome/browser/notifications/notification_display_service.h" |
| #include "testing/gmock/include/gmock/gmock.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" |
| #include "url/origin.h" |
| |
| namespace { |
| |
| class FakeNotificationBlocker : public NotificationBlocker { |
| public: |
| FakeNotificationBlocker() = default; |
| ~FakeNotificationBlocker() override = default; |
| |
| // NotificationDisplayQueue::NotificationBlocker: |
| bool ShouldBlockNotification( |
| const message_center::Notification& notification) override { |
| if (!should_block_ || !blocked_origin_) |
| return should_block_; |
| |
| return url::IsSameOriginWith(notification.origin_url(), *blocked_origin_); |
| } |
| MOCK_METHOD(void, |
| OnBlockedNotification, |
| (const message_center::Notification&, bool), |
| (override)); |
| MOCK_METHOD(void, |
| OnClosedNotification, |
| (const message_center::Notification&), |
| (override)); |
| |
| void SetShouldBlockNotifications(bool should_block) { |
| should_block_ = should_block; |
| NotifyBlockingStateChanged(); |
| } |
| |
| void SetBlockedOrigin(const std::optional<GURL>& blocked_origin) { |
| blocked_origin_ = blocked_origin; |
| NotifyBlockingStateChanged(); |
| } |
| |
| private: |
| bool should_block_ = false; |
| std::optional<GURL> blocked_origin_; |
| }; |
| |
| class NotificationDisplayServiceMock : public NotificationDisplayService { |
| public: |
| NotificationDisplayServiceMock() = default; |
| ~NotificationDisplayServiceMock() override = default; |
| |
| using NotificationDisplayService::DisplayedNotificationsCallback; |
| |
| MOCK_METHOD3(DisplayMockImpl, |
| void(NotificationHandler::Type, |
| const message_center::Notification&, |
| NotificationCommon::Metadata*)); |
| void Display( |
| NotificationHandler::Type notification_type, |
| const message_center::Notification& notification, |
| std::unique_ptr<NotificationCommon::Metadata> metadata) override { |
| DisplayMockImpl(notification_type, notification, metadata.get()); |
| } |
| |
| MOCK_METHOD2(Close, void(NotificationHandler::Type, const std::string&)); |
| MOCK_METHOD1(GetDisplayed, void(DisplayedNotificationsCallback)); |
| MOCK_METHOD2(GetDisplayedForOrigin, |
| void(const GURL& origin, DisplayedNotificationsCallback)); |
| MOCK_METHOD1(AddObserver, void(Observer* observer)); |
| MOCK_METHOD1(RemoveObserver, void(Observer* observer)); |
| }; |
| |
| // Matcher to compare Notifications |
| MATCHER_P(EqualNotification, notification, "") { |
| return arg.type() == notification.type() && arg.id() == notification.id(); |
| } |
| |
| 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(), |
| base::MakeRefCounted<message_center::NotificationDelegate>()); |
| } |
| |
| message_center::Notification CreateNotification(const std::string& id) { |
| return CreateNotification(id, GURL()); |
| } |
| |
| } // namespace |
| |
| class NotificationDisplayQueueTest : public testing::Test { |
| protected: |
| NotificationDisplayQueueTest() = default; |
| ~NotificationDisplayQueueTest() override = default; |
| |
| // testing::Test: |
| void SetUp() override { |
| auto blocker = std::make_unique<FakeNotificationBlocker>(); |
| notification_blocker_ = blocker.get(); |
| |
| NotificationDisplayQueue::NotificationBlockers blockers; |
| blockers.push_back(std::move(blocker)); |
| queue_.SetNotificationBlockers(std::move(blockers)); |
| } |
| |
| NotificationDisplayServiceMock& service() { return service_; } |
| |
| NotificationDisplayQueue& queue() { return queue_; } |
| |
| FakeNotificationBlocker& notification_blocker() { |
| return *notification_blocker_; |
| } |
| |
| private: |
| NotificationDisplayServiceMock service_; |
| NotificationDisplayQueue queue_{&service_}; |
| raw_ptr<FakeNotificationBlocker, DanglingUntriaged> notification_blocker_ = |
| nullptr; |
| }; |
| |
| TEST_F(NotificationDisplayQueueTest, ShouldEnqueueWithoutBlockers) { |
| queue().SetNotificationBlockers({}); |
| EXPECT_FALSE(queue().ShouldEnqueueNotification( |
| NotificationHandler::Type::WEB_PERSISTENT, CreateNotification("id"))); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, ShouldEnqueueWithAllowingBlocker) { |
| EXPECT_FALSE(queue().ShouldEnqueueNotification( |
| NotificationHandler::Type::WEB_PERSISTENT, CreateNotification("id"))); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, ShouldEnqueueWithBlockingBlocker) { |
| notification_blocker().SetShouldBlockNotifications(true); |
| EXPECT_TRUE(queue().ShouldEnqueueNotification( |
| NotificationHandler::Type::WEB_PERSISTENT, CreateNotification("id"))); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, ShouldEnqueueForNonWebNotification) { |
| notification_blocker().SetShouldBlockNotifications(true); |
| EXPECT_FALSE(queue().ShouldEnqueueNotification( |
| NotificationHandler::Type::TRANSIENT, CreateNotification("id"))); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, EnqueueNotification) { |
| std::string notification_id = "id"; |
| GURL origin("https://example.com"); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| CreateNotification(notification_id, origin), |
| /*metadata=*/nullptr); |
| EXPECT_THAT(queue().GetQueuedNotificationIds(), |
| testing::ElementsAre(notification_id)); |
| EXPECT_THAT(queue().GetQueuedNotificationIdsForOrigin(origin), |
| testing::ElementsAre(notification_id)); |
| EXPECT_TRUE(queue() |
| .GetQueuedNotificationIdsForOrigin(GURL("https://foo.bar")) |
| .empty()); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, RemoveQueuedNotification) { |
| std::string notification_id_1 = "id1"; |
| std::string notification_id_2 = "id2"; |
| std::string notification_id_3 = "id3"; |
| |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| CreateNotification(notification_id_1), |
| /*metadata=*/nullptr); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| CreateNotification(notification_id_2), |
| /*metadata=*/nullptr); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| CreateNotification(notification_id_3), |
| /*metadata=*/nullptr); |
| EXPECT_EQ(3u, queue().GetQueuedNotificationIds().size()); |
| |
| queue().RemoveQueuedNotification(notification_id_2); |
| std::set<std::string> queued = queue().GetQueuedNotificationIds(); |
| EXPECT_EQ(2u, queued.size()); |
| EXPECT_EQ(1u, queued.count(notification_id_1)); |
| EXPECT_EQ(1u, queued.count(notification_id_3)); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, BlockUnblockBlocker) { |
| EXPECT_CALL(service(), DisplayMockImpl).Times(0); |
| notification_blocker().SetShouldBlockNotifications(true); |
| notification_blocker().SetShouldBlockNotifications(false); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, BlockUnblockMultipleBlockers) { |
| auto blocker_1 = std::make_unique<FakeNotificationBlocker>(); |
| FakeNotificationBlocker* notification_blocker_1 = blocker_1.get(); |
| auto blocker_2 = std::make_unique<FakeNotificationBlocker>(); |
| FakeNotificationBlocker* notification_blocker_2 = blocker_2.get(); |
| |
| NotificationDisplayQueue::NotificationBlockers blockers; |
| blockers.push_back(std::move(blocker_1)); |
| blockers.push_back(std::move(blocker_2)); |
| queue().SetNotificationBlockers(std::move(blockers)); |
| |
| EXPECT_CALL(service(), DisplayMockImpl).Times(0); |
| notification_blocker_1->SetShouldBlockNotifications(true); |
| notification_blocker_2->SetShouldBlockNotifications(true); |
| |
| EXPECT_CALL(*notification_blocker_1, OnBlockedNotification); |
| EXPECT_CALL(*notification_blocker_2, OnBlockedNotification); |
| |
| message_center::Notification notification = CreateNotification("id"); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| notification, /*metadata=*/nullptr); |
| |
| notification_blocker_2->SetShouldBlockNotifications(false); |
| EXPECT_CALL(service(), DisplayMockImpl(NotificationHandler::Type::TRANSIENT, |
| EqualNotification(notification), |
| /*metadata=*/nullptr)) |
| .Times(1); |
| notification_blocker_1->SetShouldBlockNotifications(false); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, UnblockNotificationOrdering) { |
| notification_blocker().SetShouldBlockNotifications(true); |
| |
| message_center::Notification notification_1 = CreateNotification("id1"); |
| message_center::Notification notification_2 = CreateNotification("id2"); |
| message_center::Notification notification_3 = CreateNotification("id3"); |
| |
| EXPECT_CALL(notification_blocker(), OnBlockedNotification).Times(3); |
| |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| notification_1, /*metadata=*/nullptr); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| notification_2, /*metadata=*/nullptr); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| notification_3, /*metadata=*/nullptr); |
| EXPECT_EQ(3u, queue().GetQueuedNotificationIds().size()); |
| |
| testing::InSequence s; |
| EXPECT_CALL(service(), DisplayMockImpl(NotificationHandler::Type::TRANSIENT, |
| EqualNotification(notification_1), |
| /*metadata=*/nullptr)); |
| EXPECT_CALL(service(), DisplayMockImpl(NotificationHandler::Type::TRANSIENT, |
| EqualNotification(notification_2), |
| /*metadata=*/nullptr)); |
| EXPECT_CALL(service(), DisplayMockImpl(NotificationHandler::Type::TRANSIENT, |
| EqualNotification(notification_3), |
| /*metadata=*/nullptr)); |
| notification_blocker().SetShouldBlockNotifications(false); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, UnblockNotificationSubset) { |
| GURL origin_1("https://example1.com"); |
| GURL origin_2("https://example2.com"); |
| |
| auto blocker_1 = std::make_unique<FakeNotificationBlocker>(); |
| FakeNotificationBlocker* notification_blocker_1 = blocker_1.get(); |
| notification_blocker_1->SetShouldBlockNotifications(true); |
| notification_blocker_1->SetBlockedOrigin(origin_1); |
| |
| auto blocker_2 = std::make_unique<FakeNotificationBlocker>(); |
| FakeNotificationBlocker* notification_blocker_2 = blocker_2.get(); |
| notification_blocker_2->SetShouldBlockNotifications(true); |
| notification_blocker_2->SetBlockedOrigin(origin_2); |
| |
| NotificationDisplayQueue::NotificationBlockers blockers; |
| blockers.push_back(std::move(blocker_1)); |
| blockers.push_back(std::move(blocker_2)); |
| queue().SetNotificationBlockers(std::move(blockers)); |
| |
| message_center::Notification notification_1 = |
| CreateNotification("id1", origin_1); |
| message_center::Notification notification_2 = |
| CreateNotification("id2", origin_2); |
| |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| notification_1, /*metadata=*/nullptr); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| notification_2, /*metadata=*/nullptr); |
| EXPECT_EQ(2u, queue().GetQueuedNotificationIds().size()); |
| |
| // Unblocking first blocker should only show first notification. |
| EXPECT_CALL(service(), DisplayMockImpl(NotificationHandler::Type::TRANSIENT, |
| EqualNotification(notification_1), |
| /*metadata=*/nullptr)); |
| notification_blocker_1->SetShouldBlockNotifications(false); |
| testing::Mock::VerifyAndClearExpectations(&service()); |
| EXPECT_EQ(1u, queue().GetQueuedNotificationIds().size()); |
| |
| // Unblocking second blocker should show second notification. |
| EXPECT_CALL(service(), DisplayMockImpl(NotificationHandler::Type::TRANSIENT, |
| EqualNotification(notification_2), |
| /*metadata=*/nullptr)); |
| notification_blocker_2->SetShouldBlockNotifications(false); |
| testing::Mock::VerifyAndClearExpectations(&service()); |
| EXPECT_EQ(0u, queue().GetQueuedNotificationIds().size()); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, MultipleBlockersNotifyBlocked) { |
| auto blocker_1 = std::make_unique<FakeNotificationBlocker>(); |
| FakeNotificationBlocker* notification_blocker_1 = blocker_1.get(); |
| auto blocker_2 = std::make_unique<FakeNotificationBlocker>(); |
| FakeNotificationBlocker* notification_blocker_2 = blocker_2.get(); |
| |
| NotificationDisplayQueue::NotificationBlockers blockers; |
| blockers.push_back(std::move(blocker_1)); |
| blockers.push_back(std::move(blocker_2)); |
| queue().SetNotificationBlockers(std::move(blockers)); |
| |
| notification_blocker_1->SetShouldBlockNotifications(true); |
| notification_blocker_2->SetShouldBlockNotifications(false); |
| |
| EXPECT_CALL(*notification_blocker_1, OnBlockedNotification).Times(1); |
| EXPECT_CALL(*notification_blocker_2, OnBlockedNotification).Times(0); |
| |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| CreateNotification("id1"), /*metadata=*/nullptr); |
| |
| notification_blocker_2->SetShouldBlockNotifications(true); |
| notification_blocker_1->SetShouldBlockNotifications(false); |
| |
| EXPECT_CALL(*notification_blocker_1, OnBlockedNotification).Times(0); |
| EXPECT_CALL(*notification_blocker_2, OnBlockedNotification).Times(1); |
| |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| CreateNotification("id2"), /*metadata=*/nullptr); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, NotifiesReplacedNotification) { |
| message_center::Notification notification = CreateNotification("id"); |
| notification_blocker().SetShouldBlockNotifications(true); |
| |
| EXPECT_CALL(notification_blocker(), |
| OnBlockedNotification(testing::_, /*replaced=*/false)); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| notification, /*metadata=*/nullptr); |
| |
| EXPECT_CALL(notification_blocker(), |
| OnBlockedNotification(testing::_, /*replaced=*/true)); |
| EXPECT_CALL(notification_blocker(), OnClosedNotification).Times(0); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| notification, /*metadata=*/nullptr); |
| } |
| |
| TEST_F(NotificationDisplayQueueTest, NotifiesClosedNotification) { |
| message_center::Notification notification = CreateNotification("id"); |
| notification_blocker().SetShouldBlockNotifications(true); |
| |
| EXPECT_CALL(notification_blocker(), OnBlockedNotification); |
| queue().EnqueueNotification(NotificationHandler::Type::TRANSIENT, |
| notification, /*metadata=*/nullptr); |
| |
| EXPECT_CALL(notification_blocker(), OnClosedNotification); |
| queue().RemoveQueuedNotification(notification.id()); |
| } |