blob: 65a89be05cadd63c5551f454e36f112c9c365853 [file] [log] [blame]
// Copyright 2014 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 <stdint.h>
#include <memory>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/feature_list.h"
#include "base/files/scoped_temp_dir.h"
#include "base/stl_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/time/time.h"
#include "build/build_config.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_impl.h"
#include "chrome/test/base/testing_profile.h"
#include "components/history/core/browser/history_database_params.h"
#include "components/history/core/browser/history_service.h"
#include "components/history/core/test/test_history_database.h"
#include "components/ukm/test_ukm_recorder.h"
#include "content/public/test/test_browser_thread_bundle.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"
#if BUILDFLAG(ENABLE_EXTENSIONS)
#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"
#include "extensions/common/value_builder.h"
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
using blink::NotificationResources;
using blink::PlatformNotificationData;
using content::NotificationDatabaseData;
using message_center::Notification;
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 {
display_service_tester_ =
std::make_unique<NotificationDisplayServiceTester>(&profile_);
mock_logger_ = static_cast<MockNotificationMetricsLogger*>(
NotificationMetricsLoggerFactory::GetInstance()
->SetTestingFactoryAndUse(
&profile_,
base::BindRepeating(
&MockNotificationMetricsLogger::FactoryForTests)));
recorder_ = std::make_unique<ukm::TestAutoSetUkmRecorder>();
}
void TearDown() override {
display_service_tester_.reset();
}
protected:
// Returns the Platform Notification Service these unit tests are for.
PlatformNotificationServiceImpl* service() const {
return PlatformNotificationServiceImpl::GetInstance();
}
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:
content::TestBrowserThreadBundle thread_bundle_;
TestingProfile profile_;
std::unique_ptr<NotificationDisplayServiceTester> display_service_tester_;
// Owned by the |profile_| as a keyed service.
MockNotificationMetricsLogger* mock_logger_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> recorder_;
};
TEST_F(PlatformNotificationServiceTest, DisplayNonPersistentThenClose) {
PlatformNotificationData data;
data.title = base::ASCIIToUTF16("My Notification");
data.body = base::ASCIIToUTF16("Hello, world!");
service()->DisplayNotification(&profile_, kNotificationId,
GURL("https://chrome.com/"), data,
NotificationResources());
EXPECT_EQ(1u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
service()->CloseNotification(&profile_, kNotificationId);
EXPECT_EQ(0u, GetNotificationCountForType(
NotificationHandler::Type::WEB_NON_PERSISTENT));
}
TEST_F(PlatformNotificationServiceTest, DisplayPersistentThenClose) {
PlatformNotificationData data;
data.title = base::ASCIIToUTF16("My notification's title");
data.body = base::ASCIIToUTF16("Hello, world!");
EXPECT_CALL(*mock_logger_, LogPersistentNotificationShown());
service()->DisplayPersistentNotification(
&profile_, 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(&profile_, kNotificationId);
EXPECT_EQ(0u, GetNotificationCountForType(
NotificationHandler::Type::WEB_PERSISTENT));
}
TEST_F(PlatformNotificationServiceTest, DisplayNonPersistentPropertiesMatch) {
std::vector<int> vibration_pattern(
kNotificationVibrationPattern,
kNotificationVibrationPattern +
base::size(kNotificationVibrationPattern));
PlatformNotificationData data;
data.title = base::ASCIIToUTF16("My notification's title");
data.body = base::ASCIIToUTF16("Hello, world!");
data.vibration_pattern = vibration_pattern;
data.silent = true;
service()->DisplayNotification(&profile_, kNotificationId,
GURL("https://chrome.com/"), 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(
kNotificationVibrationPattern,
kNotificationVibrationPattern +
base::size(kNotificationVibrationPattern));
PlatformNotificationData data;
data.title = base::ASCIIToUTF16("My notification's title");
data.body = base::ASCIIToUTF16("Hello, world!");
data.vibration_pattern = vibration_pattern;
data.silent = true;
data.actions.resize(2);
data.actions[0].type = blink::PLATFORM_NOTIFICATION_ACTION_TYPE_BUTTON;
data.actions[0].title = base::ASCIIToUTF16("Button 1");
data.actions[1].type = blink::PLATFORM_NOTIFICATION_ACTION_TYPE_TEXT;
data.actions[1].title = base::ASCIIToUTF16("Button 2");
NotificationResources notification_resources;
notification_resources.action_icons.resize(data.actions.size());
EXPECT_CALL(*mock_logger_, LogPersistentNotificationShown());
service()->DisplayPersistentNotification(
&profile_, 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, RecordNotificationUkmEventHistory) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
HistoryServiceFactory::GetInstance()->SetTestingFactoryAndUse(
&profile_, base::BindRepeating([](content::BrowserContext* context) {
return static_cast<std::unique_ptr<KeyedService>>(
std::make_unique<history::HistoryService>());
}));
history::HistoryService* history_service =
HistoryServiceFactory::GetForProfile(&profile_,
ServiceAccessType::EXPLICIT_ACCESS);
// Initialize the |history_service| based on our |temp_dir|.
history_service->Init(
history::TestHistoryDatabaseParamsForPath(temp_dir.GetPath()));
NotificationDatabaseData data;
data.closed_reason = NotificationDatabaseData::ClosedReason::USER;
data.origin = GURL("https://chrome.com/");
size_t initial_entries_count = recorder_->entries_count();
size_t expected_entries_count = initial_entries_count + 1;
// First attempt to record an event for |data.origin| before it has been added
// to the |history_service|. Nothing should be recorded.
service()->RecordNotificationUkmEvent(&profile_, data);
{
base::RunLoop run_loop;
service()->set_history_query_complete_closure_for_testing(
run_loop.QuitClosure());
run_loop.Run();
}
EXPECT_EQ(recorder_->entries_count(), initial_entries_count);
// Now add |data.origin| to the |history_service|. After this, notification
// events being logged should end up in UKM.
history_service->AddPage(data.origin, base::Time::Now(),
history::SOURCE_BROWSED);
service()->RecordNotificationUkmEvent(&profile_, data);
{
base::RunLoop run_loop;
service()->set_history_query_complete_closure_for_testing(
run_loop.QuitClosure());
run_loop.Run();
}
EXPECT_EQ(recorder_->entries_count(), expected_entries_count);
// Delete the |data.origin| from the |history_service|. Subsequent events
// should not be logged to UKM anymore.
history_service->DeleteURL(data.origin);
service()->RecordNotificationUkmEvent(&profile_, data);
{
base::RunLoop run_loop;
service()->set_history_query_complete_closure_for_testing(
run_loop.QuitClosure());
run_loop.Run();
}
EXPECT_EQ(recorder_->entries_count(), expected_entries_count);
}
TEST_F(PlatformNotificationServiceTest, RecordNotificationUkmEvent) {
NotificationDatabaseData data;
data.notification_id = "notification1";
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;
blink::PlatformNotificationAction action1, action2, action3;
data.notification_data.actions.push_back(action1);
data.notification_data.actions.push_back(action2);
data.notification_data.actions.push_back(action3);
data.notification_data.require_interaction = false;
data.num_clicks = 3;
data.num_action_button_clicks = 1;
data.time_until_close_millis = base::TimeDelta::FromMilliseconds(10000);
data.time_until_first_click_millis = base::TimeDelta::FromMilliseconds(2222);
data.time_until_last_click_millis = base::TimeDelta::FromMilliseconds(3333);
history::URLRow url_row;
history::VisitVector visits;
// The history service does not find the given URL and nothing is recorded.
service()->OnUrlHistoryQueryComplete(data, false, url_row, visits);
std::vector<const ukm::mojom::UkmEntry*> entries =
recorder_->GetEntriesByName(ukm::builders::Notification::kEntryName);
EXPECT_EQ(0u, entries.size());
// The history service finds the given URL and the notification is logged.
service()->OnUrlHistoryQueryComplete(data, true, url_row, visits);
entries =
recorder_->GetEntriesByName(ukm::builders::Notification::kEntryName);
ASSERT_EQ(1u, entries.size());
auto* entry = entries[0];
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(&profile_);
int64_t second_id = service()->ReadNextPersistentNotificationId(&profile_);
EXPECT_LT(first_id, second_id);
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
TEST_F(PlatformNotificationServiceTest, DisplayNameForContextMessage) {
base::string16 display_name = service()->DisplayNameForContextMessage(
&profile_, GURL("https://chrome.com/"));
EXPECT_TRUE(display_name.empty());
// Create a mocked extension.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder()
.SetID("honijodknafkokifofgiaalefdiedpko")
.SetManifest(extensions::DictionaryBuilder()
.Set("name", "NotificationTest")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("description", "Test Extension")
.Build())
.Build();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(&profile_);
EXPECT_TRUE(registry->AddEnabled(extension));
display_name = service()->DisplayNameForContextMessage(
&profile_,
GURL("chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html"));
EXPECT_EQ("NotificationTest", base::UTF16ToUTF8(display_name));
}
TEST_F(PlatformNotificationServiceTest, CreateNotificationFromData) {
PlatformNotificationData notification_data;
notification_data.title = base::ASCIIToUTF16("My Notification");
notification_data.body = base::ASCIIToUTF16("Hello, world!");
GURL origin("https://chrome.com/");
Notification notification = service()->CreateNotificationFromData(
&profile_, origin, "id", notification_data, NotificationResources());
EXPECT_TRUE(notification.context_message().empty());
// Create a mocked extension.
scoped_refptr<const extensions::Extension> extension =
extensions::ExtensionBuilder()
.SetID("honijodknafkokifofgiaalefdiedpko")
.SetManifest(extensions::DictionaryBuilder()
.Set("name", "NotificationTest")
.Set("version", "1.0")
.Set("manifest_version", 2)
.Set("description", "Test Extension")
.Build())
.Build();
extensions::ExtensionRegistry* registry =
extensions::ExtensionRegistry::Get(&profile_);
EXPECT_TRUE(registry->AddEnabled(extension));
notification = service()->CreateNotificationFromData(
&profile_,
GURL("chrome-extension://honijodknafkokifofgiaalefdiedpko/main.html"),
"id", notification_data, NotificationResources());
EXPECT_EQ("NotificationTest",
base::UTF16ToUTF8(notification.context_message()));
}
#endif // BUILDFLAG(ENABLE_EXTENSIONS)