blob: 035aa2b50d7751982e9af3405c6e4cb8271c46e8 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/notifications/screen_capture_notification_blocker.h"
#include "base/optional.h"
#include "base/scoped_observer.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/browser_features.h"
#include "chrome/browser/media/webrtc/media_capture_devices_dispatcher.h"
#include "chrome/browser/media/webrtc/media_stream_capture_indicator.h"
#include "chrome/browser/notifications/muted_notification_handler.h"
#include "chrome/browser/notifications/notification_blocker.h"
#include "chrome/browser/notifications/notification_handler.h"
#include "chrome/browser/notifications/stub_notification_display_service.h"
#include "chrome/grit/generated_resources.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_web_contents_factory.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/public/cpp/notification.h"
#include "url/gurl.h"
namespace {
message_center::Notification CreateNotification(const GURL& origin,
const std::string& id) {
return message_center::Notification(
message_center::NOTIFICATION_TYPE_SIMPLE, id,
/*title=*/base::string16(),
/*message=*/base::string16(), /*icon=*/gfx::Image(),
/*display_source=*/base::string16(), origin, message_center::NotifierId(),
message_center::RichNotificationData(), /*delegate=*/nullptr);
}
message_center::Notification CreateNotification(const GURL& origin) {
return CreateNotification(origin, /*id=*/"id");
}
} // namespace
namespace {
constexpr int kShowActionIndex = 0;
} // namespace
class MockNotificationBlockerObserver : public NotificationBlocker::Observer {
public:
MockNotificationBlockerObserver() = default;
MockNotificationBlockerObserver(const MockNotificationBlockerObserver&) =
delete;
MockNotificationBlockerObserver& operator=(
const MockNotificationBlockerObserver&) = delete;
~MockNotificationBlockerObserver() override = default;
// NotificationBlocker::Observer:
MOCK_METHOD(void, OnBlockingStateChanged, (), (override));
};
class ScreenCaptureNotificationBlockerTest : public testing::Test {
public:
ScreenCaptureNotificationBlockerTest() {
scoped_feature_list_.InitAndEnableFeature(
features::kMuteNotificationsDuringScreenShare);
notification_service_ =
std::make_unique<StubNotificationDisplayService>(&profile_);
auto blocker = std::make_unique<ScreenCaptureNotificationBlocker>(
notification_service_.get());
blocker_ = blocker.get();
notification_service_->OverrideNotificationHandlerForTesting(
NotificationHandler::Type::NOTIFICATIONS_MUTED,
std::make_unique<MutedNotificationHandler>(blocker_));
NotificationDisplayQueue::NotificationBlockers blockers;
blockers.push_back(std::move(blocker));
notification_service_->SetBlockersForTesting(std::move(blockers));
}
~ScreenCaptureNotificationBlockerTest() override = default;
ScreenCaptureNotificationBlocker& blocker() { return *blocker_; }
content::WebContents* CreateWebContents(const GURL& url) {
content::WebContents* contents =
web_contents_factory_.CreateWebContents(&profile_);
content::NavigationSimulator::NavigateAndCommitFromBrowser(contents, url);
return contents;
}
void SimulateClose(bool by_user) {
base::Optional<message_center::Notification> notification =
GetMutedNotification();
ASSERT_TRUE(notification);
notification_service_->RemoveNotification(
NotificationHandler::Type::NOTIFICATIONS_MUTED, notification->id(),
by_user, /*silent=*/false);
}
void SimulateClick(const base::Optional<int>& action_index) {
base::Optional<message_center::Notification> notification =
GetMutedNotification();
ASSERT_TRUE(notification);
notification_service_->SimulateClick(
NotificationHandler::Type::NOTIFICATIONS_MUTED, notification->id(),
action_index,
/*reply=*/base::nullopt);
}
base::Optional<message_center::Notification> GetMutedNotification() {
std::vector<message_center::Notification> notifications =
notification_service_->GetDisplayedNotificationsForType(
NotificationHandler::Type::NOTIFICATIONS_MUTED);
// Only one instance of the notification should be on screen.
EXPECT_LE(notifications.size(), 1u);
if (notifications.empty())
return base::nullopt;
return notifications[0];
}
private:
content::BrowserTaskEnvironment task_environment_;
base::test::ScopedFeatureList scoped_feature_list_;
TestingProfile profile_;
content::TestWebContentsFactory web_contents_factory_;
std::unique_ptr<StubNotificationDisplayService> notification_service_;
ScreenCaptureNotificationBlocker* blocker_;
};
TEST_F(ScreenCaptureNotificationBlockerTest, ShouldNotBlockWhenNotCapturing) {
EXPECT_FALSE(blocker().ShouldBlockNotification(
CreateNotification(GURL("https://example.com"))));
}
TEST_F(ScreenCaptureNotificationBlockerTest, ShouldNotBlockCapturingOrigin) {
GURL origin1("https://example1.com");
GURL origin2("https://example2.com");
GURL origin3("https://example3.com");
// |origin1| and |origin2| are capturing, |origin3| is not.
blocker().OnIsCapturingDisplayChanged(CreateWebContents(origin1), true);
blocker().OnIsCapturingDisplayChanged(CreateWebContents(origin2), true);
EXPECT_FALSE(blocker().ShouldBlockNotification(CreateNotification(origin1)));
EXPECT_FALSE(blocker().ShouldBlockNotification(CreateNotification(origin2)));
EXPECT_TRUE(blocker().ShouldBlockNotification(CreateNotification(origin3)));
}
TEST_F(ScreenCaptureNotificationBlockerTest, ShouldBlockWhenCapturing) {
blocker().OnIsCapturingDisplayChanged(
CreateWebContents(GURL("https://example1.com")), true);
EXPECT_TRUE(blocker().ShouldBlockNotification(
CreateNotification(GURL("https://example2.com"))));
}
TEST_F(ScreenCaptureNotificationBlockerTest, ShouldBlockWhenCapturingMutliple) {
content::WebContents* contents_1 =
CreateWebContents(GURL("https://example1.com"));
content::WebContents* contents_2 =
CreateWebContents(GURL("https://example2.com"));
blocker().OnIsCapturingDisplayChanged(contents_1, true);
blocker().OnIsCapturingDisplayChanged(contents_2, true);
EXPECT_TRUE(blocker().ShouldBlockNotification(
CreateNotification(GURL("https://example3.com"))));
blocker().OnIsCapturingDisplayChanged(contents_1, false);
EXPECT_TRUE(blocker().ShouldBlockNotification(
CreateNotification(GURL("https://example3.com"))));
blocker().OnIsCapturingDisplayChanged(contents_2, false);
EXPECT_FALSE(blocker().ShouldBlockNotification(
CreateNotification(GURL("https://example3.com"))));
}
TEST_F(ScreenCaptureNotificationBlockerTest, CapturingTwice) {
content::WebContents* contents =
CreateWebContents(GURL("https://example1.com"));
// Calling changed twice with the same contents should ignore the second call.
blocker().OnIsCapturingDisplayChanged(contents, true);
blocker().OnIsCapturingDisplayChanged(contents, true);
EXPECT_TRUE(blocker().ShouldBlockNotification(
CreateNotification(GURL("https://example2.com"))));
blocker().OnIsCapturingDisplayChanged(contents, false);
EXPECT_FALSE(blocker().ShouldBlockNotification(
CreateNotification(GURL("https://example2.com"))));
}
TEST_F(ScreenCaptureNotificationBlockerTest, StopUnknownContents) {
content::WebContents* contents =
CreateWebContents(GURL("https://example1.com"));
blocker().OnIsCapturingDisplayChanged(contents, false);
EXPECT_FALSE(blocker().ShouldBlockNotification(
CreateNotification(GURL("https://example2.com"))));
}
TEST_F(ScreenCaptureNotificationBlockerTest,
ObservesMediaStreamCaptureIndicator) {
MediaStreamCaptureIndicator* indicator =
MediaCaptureDevicesDispatcher::GetInstance()
->GetMediaStreamCaptureIndicator()
.get();
EXPECT_TRUE(blocker().observer_.IsObserving(indicator));
}
TEST_F(ScreenCaptureNotificationBlockerTest, ShowsMutedNotification) {
EXPECT_FALSE(GetMutedNotification());
blocker().OnIsCapturingDisplayChanged(
CreateWebContents(GURL("https://example1.com")), true);
blocker().OnBlockedNotification(
CreateNotification(GURL("https://example2.com")), /*replaced*/ false);
base::Optional<message_center::Notification> notification =
GetMutedNotification();
ASSERT_TRUE(notification);
EXPECT_TRUE(notification->renotify());
EXPECT_EQ(message_center::NOTIFICATION_TYPE_SIMPLE, notification->type());
EXPECT_EQ(l10n_util::GetPluralStringFUTF16(IDS_NOTIFICATION_MUTED_TITLE,
/*count=*/1),
notification->title());
EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NOTIFICATION_MUTED_MESSAGE),
notification->message());
ASSERT_EQ(1u, notification->buttons().size());
EXPECT_EQ(l10n_util::GetPluralStringFUTF16(IDS_NOTIFICATION_MUTED_ACTION_SHOW,
/*count=*/1),
notification->buttons()[0].title);
}
TEST_F(ScreenCaptureNotificationBlockerTest, UpdatesMutedNotification) {
constexpr int kCount = 10;
blocker().OnIsCapturingDisplayChanged(
CreateWebContents(GURL("https://example1.com")), true);
for (int i = 0; i < kCount; ++i) {
blocker().OnBlockedNotification(
CreateNotification(GURL("https://example2.com")), /*replaced*/ false);
}
base::Optional<message_center::Notification> notification =
GetMutedNotification();
ASSERT_TRUE(notification);
EXPECT_EQ(
l10n_util::GetPluralStringFUTF16(IDS_NOTIFICATION_MUTED_TITLE, kCount),
notification->title());
EXPECT_EQ(l10n_util::GetStringUTF16(IDS_NOTIFICATION_MUTED_MESSAGE),
notification->message());
ASSERT_EQ(1u, notification->buttons().size());
EXPECT_EQ(l10n_util::GetPluralStringFUTF16(IDS_NOTIFICATION_MUTED_ACTION_SHOW,
kCount),
notification->buttons()[0].title);
}
TEST_F(ScreenCaptureNotificationBlockerTest, ClosesMutedNotification) {
content::WebContents* contents =
CreateWebContents(GURL("https://example1.com"));
// No notification initially.
blocker().OnIsCapturingDisplayChanged(contents, true);
EXPECT_FALSE(GetMutedNotification());
// Expect a notification once we block one.
blocker().OnBlockedNotification(
CreateNotification(GURL("https://example2.com")), /*replaced*/ false);
EXPECT_TRUE(GetMutedNotification());
// Expect notification to be closed when capturing stops.
blocker().OnIsCapturingDisplayChanged(contents, false);
EXPECT_FALSE(GetMutedNotification());
}
TEST_F(ScreenCaptureNotificationBlockerTest,
ClosesMutedNotificationOnBodyClick) {
blocker().OnIsCapturingDisplayChanged(
CreateWebContents(GURL("https://example1.com")), true);
blocker().OnBlockedNotification(
CreateNotification(GURL("https://example2.com")), /*replaced*/ false);
// Expect notification to be closed after clicking on its body.
SimulateClick(/*action_index=*/base::nullopt);
EXPECT_FALSE(GetMutedNotification());
}
TEST_F(ScreenCaptureNotificationBlockerTest, ShowsMutedNotificationAfterClose) {
blocker().OnIsCapturingDisplayChanged(
CreateWebContents(GURL("https://example1.com")), true);
blocker().OnBlockedNotification(
CreateNotification(GURL("https://example2.com")), /*replaced*/ false);
// Blocking another notification after closing the muted one should show a new
// one with an updated message.
SimulateClick(/*action_index=*/base::nullopt);
blocker().OnBlockedNotification(
CreateNotification(GURL("https://example2.com")), /*replaced*/ false);
base::Optional<message_center::Notification> notification =
GetMutedNotification();
ASSERT_TRUE(notification);
EXPECT_EQ(l10n_util::GetPluralStringFUTF16(IDS_NOTIFICATION_MUTED_TITLE,
/*count=*/2),
notification->title());
}
TEST_F(ScreenCaptureNotificationBlockerTest, ShowAction) {
MockNotificationBlockerObserver observer;
ScopedObserver<NotificationBlocker, NotificationBlocker::Observer>
scoped_observer(&observer);
scoped_observer.Add(&blocker());
EXPECT_CALL(observer, OnBlockingStateChanged);
blocker().OnIsCapturingDisplayChanged(
CreateWebContents(GURL("https://example1.com")), true);
testing::Mock::VerifyAndClearExpectations(&observer);
message_center::Notification notification =
CreateNotification(GURL("https://example2.com"));
blocker().OnBlockedNotification(notification, /*replaced*/ false);
EXPECT_TRUE(blocker().ShouldBlockNotification(notification));
// Showing should close the "Notifications muted" notification and allow
// showing future web notifications.
EXPECT_CALL(observer, OnBlockingStateChanged);
SimulateClick(kShowActionIndex);
testing::Mock::VerifyAndClearExpectations(&observer);
EXPECT_FALSE(GetMutedNotification());
EXPECT_FALSE(blocker().ShouldBlockNotification(notification));
}
TEST_F(ScreenCaptureNotificationBlockerTest, CloseHistogram) {
base::HistogramTester histogram_tester;
const char kHistogram[] = "Notifications.Blocker.ScreenCapture.Action.Close";
blocker().OnIsCapturingDisplayChanged(
CreateWebContents(GURL("https://example1.com")), true);
message_center::Notification notification =
CreateNotification(GURL("https://example2.com"));
blocker().OnBlockedNotification(notification, /*replaced*/ false);
SimulateClose(/*by_user=*/true);
histogram_tester.ExpectBucketCount(kHistogram, /*sample=*/1, /*count=*/1);
histogram_tester.ExpectTotalCount(kHistogram, /*count=*/1);
blocker().OnBlockedNotification(notification, /*replaced*/ false);
SimulateClose(/*by_user=*/true);
histogram_tester.ExpectBucketCount(kHistogram, /*sample=*/2, /*count=*/1);
histogram_tester.ExpectTotalCount(kHistogram, /*count=*/2);
blocker().OnBlockedNotification(notification, /*replaced*/ false);
SimulateClose(/*by_user=*/false);
histogram_tester.ExpectTotalCount(kHistogram, /*count=*/2);
}
TEST_F(ScreenCaptureNotificationBlockerTest, BodyClickHistogram) {
base::HistogramTester histogram_tester;
const char kHistogram[] = "Notifications.Blocker.ScreenCapture.Action.Body";
blocker().OnIsCapturingDisplayChanged(
CreateWebContents(GURL("https://example1.com")), true);
message_center::Notification notification =
CreateNotification(GURL("https://example2.com"));
blocker().OnBlockedNotification(notification, /*replaced*/ false);
SimulateClick(/*action_index=*/base::nullopt);
histogram_tester.ExpectBucketCount(kHistogram, /*sample=*/1, /*count=*/1);
histogram_tester.ExpectTotalCount(kHistogram, /*count=*/1);
blocker().OnBlockedNotification(notification, /*replaced*/ false);
SimulateClick(/*action_index=*/base::nullopt);
histogram_tester.ExpectBucketCount(kHistogram, /*sample=*/2, /*count=*/1);
histogram_tester.ExpectTotalCount(kHistogram, /*count=*/2);
}
TEST_F(ScreenCaptureNotificationBlockerTest, ShowClickHistogram) {
base::HistogramTester histogram_tester;
blocker().OnIsCapturingDisplayChanged(
CreateWebContents(GURL("https://example1.com")), true);
message_center::Notification notification =
CreateNotification(GURL("https://example2.com"));
blocker().OnBlockedNotification(notification, /*replaced*/ false);
SimulateClick(kShowActionIndex);
histogram_tester.ExpectUniqueSample(
"Notifications.Blocker.ScreenCapture.Action.Show", /*sample=*/1,
/*count=*/1);
}
TEST_F(ScreenCaptureNotificationBlockerTest, SessionEndHistograms) {
base::HistogramTester histogram_tester;
content::WebContents* contents =
CreateWebContents(GURL("https://example1.com"));
blocker().OnIsCapturingDisplayChanged(contents, true);
message_center::Notification notification1 =
CreateNotification(GURL("https://example2.com"), "id1");
message_center::Notification notification2 =
CreateNotification(GURL("https://example2.com"), "id2");
message_center::Notification notification3 =
CreateNotification(GURL("https://example2.com"), "id3");
// Mute 3 unique notifications.
blocker().OnBlockedNotification(notification1, /*replaced*/ false);
blocker().OnBlockedNotification(notification2, /*replaced*/ false);
blocker().OnBlockedNotification(notification3, /*replaced*/ false);
// Replace 2 of the muted notifications.
blocker().OnBlockedNotification(notification1, /*replaced*/ true);
blocker().OnBlockedNotification(notification2, /*replaced*/ true);
// Close 1 of the muted notifications.
blocker().OnClosedNotification(notification1);
blocker().OnIsCapturingDisplayChanged(contents, false);
histogram_tester.ExpectUniqueSample(
"Notifications.Blocker.ScreenCapture.MutedCount", /*sample=*/3,
/*count=*/1);
histogram_tester.ExpectUniqueSample(
"Notifications.Blocker.ScreenCapture.ReplacedCount", /*sample=*/2,
/*count=*/1);
histogram_tester.ExpectUniqueSample(
"Notifications.Blocker.ScreenCapture.ClosedCount", /*sample=*/1,
/*count=*/1);
}