blob: 9509359d1ff276962815618172cc10790373a092 [file] [log] [blame]
// Copyright 2017 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 <utility>
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/observer_list.h"
#include "build/build_config.h"
#include "build/buildflag.h"
#include "chrome/browser/notifications/non_persistent_notification_handler.h"
#include "chrome/browser/notifications/notification_display_service_factory.h"
#include "chrome/browser/notifications/persistent_notification_handler.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/updates/announcement_notification/announcement_notification_handler.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/safe_browsing/buildflags.h"
#include "content/public/browser/browser_thread.h"
#include "extensions/buildflags/buildflags.h"
#include "ui/message_center/public/cpp/notification.h"
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
#include "chrome/browser/extensions/api/notifications/extension_notification_handler.h"
#endif
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \
BUILDFLAG(IS_WIN)
#include "chrome/browser/send_tab_to_self/desktop_notification_handler.h"
#include "chrome/browser/sharing/sharing_notification_handler.h"
#endif
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/nearby_sharing/nearby_notification_handler.h"
#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
#if BUILDFLAG(SAFE_BROWSING_AVAILABLE)
#include "chrome/browser/safe_browsing/tailored_security/notification_handler_desktop.h"
#endif
// static
NotificationDisplayServiceImpl* NotificationDisplayServiceImpl::GetForProfile(
Profile* profile) {
return static_cast<NotificationDisplayServiceImpl*>(
NotificationDisplayServiceFactory::GetForProfile(profile));
}
// static
void NotificationDisplayServiceImpl::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
#if BUILDFLAG(IS_LINUX)
registry->RegisterBooleanPref(prefs::kAllowSystemNotifications, true);
#endif
}
NotificationDisplayServiceImpl::NotificationDisplayServiceImpl(Profile* profile)
: profile_(profile) {
// TODO(peter): Move these to the NotificationDisplayServiceFactory.
if (profile_) {
AddNotificationHandler(
NotificationHandler::Type::WEB_NON_PERSISTENT,
std::make_unique<NonPersistentNotificationHandler>());
AddNotificationHandler(NotificationHandler::Type::WEB_PERSISTENT,
std::make_unique<PersistentNotificationHandler>());
#if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \
BUILDFLAG(IS_WIN)
AddNotificationHandler(
NotificationHandler::Type::SEND_TAB_TO_SELF,
std::make_unique<send_tab_to_self::DesktopNotificationHandler>(
profile_));
#endif
#if (BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_MAC) || \
BUILDFLAG(IS_WIN)) && \
BUILDFLAG(SAFE_BROWSING_AVAILABLE)
AddNotificationHandler(
NotificationHandler::Type::TAILORED_SECURITY,
std::make_unique<safe_browsing::TailoredSecurityNotificationHandler>());
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS_CORE)
AddNotificationHandler(
NotificationHandler::Type::EXTENSION,
std::make_unique<extensions::ExtensionNotificationHandler>());
#endif
#if !BUILDFLAG(IS_ANDROID)
AddNotificationHandler(NotificationHandler::Type::SHARING,
std::make_unique<SharingNotificationHandler>());
AddNotificationHandler(NotificationHandler::Type::ANNOUNCEMENT,
std::make_unique<AnnouncementNotificationHandler>());
auto screen_capture_blocker =
std::make_unique<ScreenCaptureNotificationBlocker>(this);
AddNotificationHandler(NotificationHandler::Type::NOTIFICATIONS_MUTED,
std::make_unique<MutedNotificationHandler>(
screen_capture_blocker.get()));
notification_queue_.AddNotificationBlocker(
std::move(screen_capture_blocker));
#endif
#if BUILDFLAG(IS_CHROMEOS)
if (NearbySharingServiceFactory::IsNearbyShareSupportedForBrowserContext(
profile_)) {
AddNotificationHandler(NotificationHandler::Type::NEARBY_SHARE,
std::make_unique<NearbyNotificationHandler>());
}
#endif
}
bridge_delegator_ = std::make_unique<NotificationPlatformBridgeDelegator>(
profile_,
base::BindOnce(
&NotificationDisplayServiceImpl::OnNotificationPlatformBridgeReady,
weak_factory_.GetWeakPtr()));
}
NotificationDisplayServiceImpl::~NotificationDisplayServiceImpl() {
for (auto& obs : observers_)
obs.OnNotificationDisplayServiceDestroyed(this);
}
void NotificationDisplayServiceImpl::ProcessNotificationOperation(
NotificationOperation operation,
NotificationHandler::Type notification_type,
const GURL& origin,
const std::string& notification_id,
const std::optional<int>& action_index,
const std::optional<std::u16string>& reply,
const std::optional<bool>& by_user,
base::OnceClosure on_completed_cb) {
NotificationHandler* handler = GetNotificationHandler(notification_type);
DCHECK(handler);
if (!handler) {
LOG(ERROR) << "Unable to find a handler for "
<< static_cast<int>(notification_type);
std::move(on_completed_cb).Run();
return;
}
switch (operation) {
case NotificationOperation::kClick:
handler->OnClick(profile_, origin, notification_id, action_index, reply,
std::move(on_completed_cb));
break;
case NotificationOperation::kClose:
DCHECK(by_user.has_value());
handler->OnClose(profile_, origin, notification_id, by_user.value(),
std::move(on_completed_cb));
for (auto& observer : observers_)
observer.OnNotificationClosed(notification_id);
break;
case NotificationOperation::kDisablePermission:
handler->DisableNotifications(profile_, origin, notification_id);
break;
case NotificationOperation::kSettings:
handler->OpenSettings(profile_, origin);
break;
case NotificationOperation::kReportAsSafe:
handler->ReportNotificationAsSafe(notification_id, origin, profile_);
break;
case NotificationOperation::kReportWarnedAsSpam:
handler->ReportWarnedNotificationAsSpam(notification_id, origin,
profile_);
break;
case NotificationOperation::kReportUnwarnedAsSpam:
handler->ReportUnwarnedNotificationAsSpam(notification_id, origin,
profile_);
break;
case NotificationOperation::kShowOriginalNotification:
handler->OnShowOriginalNotification(origin, notification_id, profile_);
break;
}
}
void NotificationDisplayServiceImpl::AddNotificationHandler(
NotificationHandler::Type notification_type,
std::unique_ptr<NotificationHandler> handler) {
DCHECK(handler);
DCHECK_EQ(notification_handlers_.count(notification_type), 0u);
notification_handlers_[notification_type] = std::move(handler);
}
NotificationHandler* NotificationDisplayServiceImpl::GetNotificationHandler(
NotificationHandler::Type notification_type) {
auto found = notification_handlers_.find(notification_type);
if (found != notification_handlers_.end())
return found->second.get();
return nullptr;
}
void NotificationDisplayServiceImpl::Shutdown() {
bridge_delegator_->DisplayServiceShutDown();
}
void NotificationDisplayServiceImpl::Display(
NotificationHandler::Type notification_type,
const message_center::Notification& notification,
std::unique_ptr<NotificationCommon::Metadata> metadata) {
// TODO(estade): in the future, the reverse should also be true: a
// non-TRANSIENT type implies no delegate.
if (notification_type == NotificationHandler::Type::TRANSIENT)
DCHECK(notification.delegate());
CHECK(profile_ || notification_type == NotificationHandler::Type::TRANSIENT);
if (!bridge_delegator_initialized_) {
actions_.push(base::BindOnce(&NotificationDisplayServiceImpl::Display,
weak_factory_.GetWeakPtr(), notification_type,
notification, std::move(metadata)));
return;
}
for (auto& observer : observers_)
observer.OnNotificationDisplayed(notification, metadata.get());
if (notification_queue_.ShouldEnqueueNotification(notification_type,
notification)) {
notification_queue_.EnqueueNotification(notification_type, notification,
std::move(metadata));
} else {
bridge_delegator_->Display(notification_type, notification,
std::move(metadata));
}
NotificationHandler* handler = GetNotificationHandler(notification_type);
if (handler)
handler->OnShow(profile_, notification.id());
}
void NotificationDisplayServiceImpl::Close(
NotificationHandler::Type notification_type,
const std::string& notification_id) {
CHECK(profile_ || notification_type == NotificationHandler::Type::TRANSIENT);
if (!bridge_delegator_initialized_) {
actions_.push(base::BindOnce(&NotificationDisplayServiceImpl::Close,
weak_factory_.GetWeakPtr(), notification_type,
notification_id));
return;
}
notification_queue_.RemoveQueuedNotification(notification_id);
bridge_delegator_->Close(notification_type, notification_id);
}
void NotificationDisplayServiceImpl::GetDisplayed(
DisplayedNotificationsCallback callback) {
if (!bridge_delegator_initialized_) {
actions_.push(base::BindOnce(&NotificationDisplayServiceImpl::GetDisplayed,
weak_factory_.GetWeakPtr(),
std::move(callback)));
return;
}
bridge_delegator_->GetDisplayed(
base::BindOnce(&NotificationDisplayServiceImpl::OnGetDisplayed,
weak_factory_.GetWeakPtr(), /*origin=*/std::nullopt,
std::move(callback)));
}
void NotificationDisplayServiceImpl::GetDisplayedForOrigin(
const GURL& origin,
DisplayedNotificationsCallback callback) {
if (!bridge_delegator_initialized_) {
actions_.push(base::BindOnce(
&NotificationDisplayServiceImpl::GetDisplayedForOrigin,
weak_factory_.GetWeakPtr(), origin, std::move(callback)));
return;
}
bridge_delegator_->GetDisplayedForOrigin(
origin,
base::BindOnce(&NotificationDisplayServiceImpl::OnGetDisplayed,
weak_factory_.GetWeakPtr(), origin, std::move(callback)));
}
void NotificationDisplayServiceImpl::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void NotificationDisplayServiceImpl::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
// Callback to run once the profile has been loaded in order to perform a
// given |operation| in a notification.
void NotificationDisplayServiceImpl::ProfileLoadedCallback(
NotificationOperation operation,
NotificationHandler::Type notification_type,
const GURL& origin,
const std::string& notification_id,
const std::optional<int>& action_index,
const std::optional<std::u16string>& reply,
const std::optional<bool>& by_user,
base::OnceClosure on_completed_cb,
Profile* profile) {
base::UmaHistogramBoolean("Notifications.LoadProfileResult",
profile != nullptr);
if (!profile) {
LOG(WARNING) << "Profile not loaded correctly";
std::move(on_completed_cb).Run();
return;
}
NotificationDisplayServiceImpl* display_service =
NotificationDisplayServiceImpl::GetForProfile(profile);
display_service->ProcessNotificationOperation(
operation, notification_type, origin, notification_id, action_index,
reply, by_user, std::move(on_completed_cb));
}
void NotificationDisplayServiceImpl::SetBlockersForTesting(
NotificationDisplayQueue::NotificationBlockers blockers) {
notification_queue_.SetNotificationBlockers(std::move(blockers));
}
void NotificationDisplayServiceImpl::
SetNotificationPlatformBridgeDelegatorForTesting(
std::unique_ptr<NotificationPlatformBridgeDelegator> bridge_delegator) {
bridge_delegator_ = std::move(bridge_delegator);
OnNotificationPlatformBridgeReady();
}
void NotificationDisplayServiceImpl::OverrideNotificationHandlerForTesting(
NotificationHandler::Type notification_type,
std::unique_ptr<NotificationHandler> handler) {
DCHECK(handler);
DCHECK_EQ(1u, notification_handlers_.count(notification_type));
notification_handlers_[notification_type] = std::move(handler);
}
void NotificationDisplayServiceImpl::OnNotificationPlatformBridgeReady() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
bridge_delegator_initialized_ = true;
// Flush any pending actions that have yet to execute.
while (!actions_.empty()) {
std::move(actions_.front()).Run();
actions_.pop();
}
}
void NotificationDisplayServiceImpl::OnGetDisplayed(
std::optional<GURL> origin,
DisplayedNotificationsCallback callback,
std::set<std::string> notification_ids,
bool supports_synchronization) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
std::set<std::string> queued =
origin.has_value()
? notification_queue_.GetQueuedNotificationIdsForOrigin(*origin)
: notification_queue_.GetQueuedNotificationIds();
notification_ids.insert(queued.begin(), queued.end());
std::move(callback).Run(std::move(notification_ids),
supports_synchronization);
}