// 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_platform_bridge_win.h"
#include <objbase.h>
#include <wrl/event.h>
#include <map>
#include <memory>
#include <set>
#include <string_view>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/hash/hash.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/win/core_winrt_util.h"
#include "base/win/registry.h"
#include "base/win/scoped_hstring.h"
#include "base/win/scoped_winrt_initializer.h"
#include "base/win/windows_version.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/notification_common.h"
#include "chrome/browser/notifications/notification_display_service_impl.h"
#include "chrome/browser/notifications/notification_handler.h"
#include "chrome/browser/notifications/win/notification_metrics.h"
#include "chrome/browser/notifications/win/notification_template_builder.h"
#include "chrome/browser/notifications/win/notification_util.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/themes/theme_service.h"
#include "chrome/browser/themes/theme_service_factory.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/notifications/notification_image_retainer.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/shell_util.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "ui/message_center/public/cpp/notification.h"
#include "url/origin.h"
namespace mswr = Microsoft::WRL;
namespace winfoundtn = ABI::Windows::Foundation;
namespace winui = ABI::Windows::UI;
namespace winxml = ABI::Windows::Data::Xml;
using base::win::ScopedHString;
using message_center::RichNotificationData;
using notifications_uma::ActivationStatus;
using notifications_uma::CloseStatus;
using notifications_uma::DisplayStatus;
using notifications_uma::GetDisplayedLaunchIdStatus;
using notifications_uma::GetDisplayedStatus;
using notifications_uma::GetNotificationLaunchIdStatus;
using notifications_uma::GetSettingPolicy;
using notifications_uma::GetSettingStatus;
using notifications_uma::HandleEventStatus;
using notifications_uma::HistoryStatus;
using notifications_uma::OnFailedStatus;
using notifications_uma::SetReadyCallbackStatus;
namespace {
// This string needs to be max 16 characters to work on Windows 10 prior to
// applying Creators Update (build 15063).
constexpr wchar_t kGroup[] = L"Notifications";
typedef winfoundtn::ITypedEventHandler<
winui::Notifications::ToastNotification*,
winui::Notifications::ToastDismissedEventArgs*>
ToastDismissedHandler;
typedef winfoundtn::ITypedEventHandler<
winui::Notifications::ToastNotification*,
winui::Notifications::ToastFailedEventArgs*>
ToastFailedHandler;
// Templated wrapper for winfoundtn::GetActivationFactory().
template <unsigned int size>
HRESULT CreateActivationFactory(wchar_t const (&class_name)[size],
const IID& iid,
void** factory) {
ScopedHString ref_class_name =
ScopedHString::Create(std::wstring_view(class_name, size - 1));
return base::win::RoGetActivationFactory(ref_class_name.get(), iid, factory);
}
void ForwardNotificationOperationOnUiThread(
NotificationOperation operation,
NotificationHandler::Type notification_type,
const GURL& origin,
const std::string& notification_id,
const std::string& profile_id,
bool incognito,
const std::optional<int>& action_index,
const std::optional<std::u16string>& reply,
const std::optional<bool>& by_user) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
if (!g_browser_process || g_browser_process->IsShuttingDown()) {
return;
}
// Profile ID can be empty for system notifications, which are not bound to a
// profile, but system notifications are transient and thus not handled by
// this NotificationPlatformBridge.
// When transient notifications are supported, this should route the
// notification response to the system NotificationDisplayService.
DCHECK(!profile_id.empty());
g_browser_process->profile_manager()->LoadProfile(
NotificationPlatformBridge::GetProfileBaseNameFromProfileId(profile_id),
incognito,
base::BindOnce(&NotificationDisplayServiceImpl::ProfileLoadedCallback,
operation, notification_type, origin, notification_id,
action_index, reply, by_user));
}
GetSettingPolicy ConvertSettingPolicy(
winui::Notifications::NotificationSetting setting) {
switch (setting) {
case winui::Notifications::NotificationSetting_Enabled:
return GetSettingPolicy::kEnabled;
case winui::Notifications::NotificationSetting_DisabledForApplication:
DLOG(ERROR) << "Notifications disabled for application";
return GetSettingPolicy::kDisabledForApplication;
case winui::Notifications::NotificationSetting_DisabledForUser:
DLOG(ERROR) << "Notifications disabled for user";
return GetSettingPolicy::kDisabledForUser;
case winui::Notifications::NotificationSetting_DisabledByGroupPolicy:
DLOG(ERROR) << "Notifications disabled by group policy";
return GetSettingPolicy::kDisabledByGroupPolicy;
case winui::Notifications::NotificationSetting_DisabledByManifest:
DLOG(ERROR) << "Notifications disabled by manifest";
return GetSettingPolicy::kDisabledByManifest;
}
DLOG(ERROR) << "Unknown Windows notification setting";
return GetSettingPolicy::kUnknown;
}
} // namespace
// static
std::unique_ptr<NotificationPlatformBridge>
NotificationPlatformBridge::Create() {
return std::make_unique<NotificationPlatformBridgeWin>();
}
// static
bool NotificationPlatformBridge::CanHandleType(
NotificationHandler::Type notification_type) {
return notification_type != NotificationHandler::Type::TRANSIENT;
}
class NotificationPlatformBridgeWinImpl
: public base::RefCountedThreadSafe<NotificationPlatformBridgeWinImpl>,
public content::BrowserThread::DeleteOnThread<
content::BrowserThread::UI> {
public:
explicit NotificationPlatformBridgeWinImpl(
scoped_refptr<base::SequencedTaskRunner> notification_task_runner)
: notification_task_runner_(std::move(notification_task_runner)),
image_retainer_(std::make_unique<NotificationImageRetainer>()) {
// Delete any remaining temp files in the image folder from the previous
// sessions.
DCHECK(notification_task_runner_);
content::BrowserThread::PostBestEffortTask(
FROM_HERE, notification_task_runner_,
image_retainer_->GetCleanupTask());
notification_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&NotificationPlatformBridgeWinImpl::InitializeOnTaskRunner,
base::Unretained(this)));
}
NotificationPlatformBridgeWinImpl(const NotificationPlatformBridgeWinImpl&) =
delete;
NotificationPlatformBridgeWinImpl& operator=(
const NotificationPlatformBridgeWinImpl&) = delete;
// Obtain an IToastNotification interface from a given XML as in
// |xml_template|. This function is only used when displaying notification in
// production code, which explains why the UMA metrics record within are
// classified with the display path.
mswr::ComPtr<winui::Notifications::IToastNotification> GetToastNotification(
const message_center::Notification& notification,
const std::wstring& xml_template,
const std::string& profile_id,
const std::wstring& app_user_model_id,
bool incognito) {
ScopedHString ref_class_name =
ScopedHString::Create(RuntimeClass_Windows_Data_Xml_Dom_XmlDocument);
mswr::ComPtr<IInspectable> inspectable;
HRESULT hr =
base::win::RoActivateInstance(ref_class_name.get(), &inspectable);
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kRoActivateFailed);
DLOG(ERROR) << "Unable to activate the XML Document " << std::hex << hr;
return nullptr;
}
mswr::ComPtr<winxml::Dom::IXmlDocumentIO> document_io;
hr = inspectable.As(&document_io);
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kConversionFailedInspectableToXmlIo);
DLOG(ERROR) << "Failed to get XmlDocument as IXmlDocumentIO " << std::hex
<< hr;
return nullptr;
}
ScopedHString ref_template = ScopedHString::Create(xml_template);
hr = document_io->LoadXml(ref_template.get());
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kLoadXmlFailed);
DLOG(ERROR) << "Unable to load the template's XML into the document "
<< std::hex << hr;
return nullptr;
}
mswr::ComPtr<winxml::Dom::IXmlDocument> document;
hr = document_io.As<winxml::Dom::IXmlDocument>(&document);
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kConversionFailedXmlIoToXml);
DLOG(ERROR) << "Unable to get as XMLDocument " << std::hex << hr;
return nullptr;
}
mswr::ComPtr<winui::Notifications::IToastNotificationFactory>
toast_notification_factory;
hr = CreateActivationFactory(
RuntimeClass_Windows_UI_Notifications_ToastNotification,
IID_PPV_ARGS(&toast_notification_factory));
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kCreateFactoryFailed);
DLOG(ERROR) << "Unable to create the IToastNotificationFactory "
<< std::hex << hr;
return nullptr;
}
mswr::ComPtr<winui::Notifications::IToastNotification> toast_notification;
hr = toast_notification_factory->CreateToastNotification(
document.Get(), &toast_notification);
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kCreateToastNotificationFailed);
DLOG(ERROR) << "Unable to create the IToastNotification " << std::hex
<< hr;
return nullptr;
}
mswr::ComPtr<winui::Notifications::IToastNotification2> toast2;
hr = toast_notification->QueryInterface(IID_PPV_ARGS(&toast2));
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kCreateToastNotification2Failed);
DLOG(ERROR) << "Failed to get IToastNotification2 object " << std::hex
<< hr;
return nullptr;
}
// Set the Group and Tag values for the notification, in order to support
// closing/replacing notification by tag. Both of these values have a limit
// of 64 characters, which is problematic because they are out of our
// control and Tag can contain just about anything. Therefore we use a hash
// of the Tag value to produce uniqueness that fits within the specified
// limits. Although Group is hard-coded, uniqueness is guaranteed through
// features providing a sufficiently distinct notification id, profile id,
// app user model id, and incognito status combinations.
ScopedHString group = ScopedHString::Create(kGroup);
ScopedHString tag = ScopedHString::Create(
GetTag(notification.id(), profile_id, app_user_model_id, incognito));
hr = toast2->put_Group(group.get());
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kSettingGroupFailed);
DLOG(ERROR) << "Failed to set Group " << std::hex << hr;
return nullptr;
}
hr = toast2->put_Tag(tag.get());
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kSettingTagFailed);
DLOG(ERROR) << "Failed to set Tag " << std::hex << hr;
return nullptr;
}
// By default, Windows 10 will always show the notification on screen.
// Chrome, however, wants to suppress them if both conditions are true:
// 1) Renotify flag is not set.
// 2) They are not new (no other notification with same tag is found).
if (!notification.renotify()) {
std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>
notifications = GetNotifications();
for (const auto& n : notifications) {
mswr::ComPtr<winui::Notifications::IToastNotification2> t2;
hr = n->QueryInterface(IID_PPV_ARGS(&t2));
if (FAILED(hr))
continue;
HSTRING hstring_group;
hr = t2->get_Group(&hstring_group);
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kGetGroupFailed);
DLOG(ERROR) << "Failed to get group value " << std::hex << hr;
return nullptr;
}
ScopedHString scoped_group(hstring_group);
HSTRING hstring_tag;
hr = t2->get_Tag(&hstring_tag);
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kGetTagFailed);
DLOG(ERROR) << "Failed to get tag value " << std::hex << hr;
return nullptr;
}
ScopedHString scoped_tag(hstring_tag);
if (group.Get() != scoped_group.Get() || tag.Get() != scoped_tag.Get())
continue; // Because it is not a repeat of an toast.
hr = toast2->put_SuppressPopup(true);
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kSuppressPopupFailed);
DLOG(ERROR) << "Failed to set suppress value " << std::hex << hr;
return nullptr;
}
}
}
return toast_notification;
}
void Display(NotificationHandler::Type notification_type,
const std::string& profile_id,
bool incognito,
std::unique_ptr<message_center::Notification> notification,
std::unique_ptr<NotificationCommon::Metadata> metadata) {
// TODO(finnur): Move this to a RoInitialized thread, as per
// crbug.com/761039.
DCHECK(notification_task_runner_->RunsTasksInCurrentSequence());
std::wstring app_user_model_id = GetAppId();
if (!notifier_for_testing_ && !notifier_.Get() &&
FAILED(InitializeToastNotifier(app_user_model_id))) {
// A histogram should have already been logged for this failure.
DLOG(ERROR) << "Unable to initialize toast notifier";
return;
}
winui::Notifications::IToastNotifier* notifier =
notifier_for_testing_ ? notifier_for_testing_ : notifier_.Get();
winui::Notifications::NotificationSetting setting;
HRESULT hr = notifier->get_Setting(&setting);
if (SUCCEEDED(hr)) {
LogGetSettingStatus(GetSettingStatus::kSuccess);
LogGetSettingPolicy(ConvertSettingPolicy(setting));
} else {
LogGetSettingStatus(GetSettingStatus::kUnknownFailure);
}
NotificationLaunchId launch_id(notification_type, notification->id(),
profile_id, app_user_model_id, incognito,
notification->origin_url());
std::wstring xml_template = BuildNotificationTemplate(
image_retainer_.get(), launch_id, *notification);
mswr::ComPtr<winui::Notifications::IToastNotification> toast =
GetToastNotification(*notification, xml_template, profile_id,
app_user_model_id, incognito);
if (!toast)
return;
// Activation via user interaction with the toast is handled in
// HandleActivation() by way of the notification_helper.
auto failed_handler = mswr::Callback<ToastFailedHandler>(
this, &NotificationPlatformBridgeWinImpl::OnFailed);
EventRegistrationToken failed_token;
hr = toast->add_Failed(failed_handler.Get(), &failed_token);
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kAddToastErrorHandlerFailed);
DLOG(ERROR) << "Unable to add toast failed event handler " << std::hex
<< hr;
return;
}
hr = notifier->Show(toast.Get());
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kShowingToastFailed);
base::UmaHistogramSparse("Notifications.Windows.ShowFailedErrorCode", hr);
DLOG(ERROR) << "Unable to display the notification " << std::hex << hr;
} else {
// Store locally the notification launch id and use it to synchronize
// browser notifications with the displayed notifications to simulate
// close events.
displayed_notifications_[{profile_id, notification->id(),
app_user_model_id}] =
GetNotificationLaunchId(toast.Get());
LogDisplayHistogram(DisplayStatus::kSuccess);
MaybeStartNotificationSynchronizationTimer();
}
}
void Close(const std::string& profile_id,
bool incognito,
const std::string& notification_id) {
DCHECK(notification_task_runner_->RunsTasksInCurrentSequence());
mswr::ComPtr<winui::Notifications::IToastNotificationHistory> history =
GetIToastNotificationHistory();
if (!history) {
LogCloseHistogram(CloseStatus::kGetToastHistoryFailed);
DLOG(ERROR) << "Failed to get IToastNotificationHistory";
return;
}
std::wstring app_user_model_id = GetAppId();
ScopedHString application_id = ScopedHString::Create(app_user_model_id);
ScopedHString group = ScopedHString::Create(kGroup);
ScopedHString tag = ScopedHString::Create(
GetTag(notification_id, profile_id, app_user_model_id, incognito));
HRESULT hr = history->RemoveGroupedTagWithId(tag.get(), group.get(),
application_id.get());
if (FAILED(hr)) {
LogCloseHistogram(CloseStatus::kRemovingToastFailed);
DLOG(ERROR) << "Failed to remove notification with id "
<< notification_id.c_str() << " " << std::hex << hr;
} else {
// We expect the notification to be removed from the action center now.
displayed_notifications_.erase(
{profile_id, notification_id, app_user_model_id});
LogCloseHistogram(CloseStatus::kSuccess);
}
}
[[nodiscard]] mswr::ComPtr<winui::Notifications::IToastNotificationHistory>
GetIToastNotificationHistory() const {
mswr::ComPtr<winui::Notifications::IToastNotificationManagerStatics>
toast_manager;
HRESULT hr = CreateActivationFactory(
RuntimeClass_Windows_UI_Notifications_ToastNotificationManager,
IID_PPV_ARGS(&toast_manager));
if (FAILED(hr)) {
LogHistoryHistogram(HistoryStatus::kCreateToastNotificationManagerFailed);
DLOG(ERROR) << "Unable to create the ToastNotificationManager "
<< std::hex << hr;
return nullptr;
}
mswr::ComPtr<winui::Notifications::IToastNotificationManagerStatics2>
toast_manager2;
hr = toast_manager.As(&toast_manager2);
if (FAILED(hr)) {
LogHistoryHistogram(HistoryStatus::kQueryToastManagerStatistics2Failed);
DLOG(ERROR) << "Failed to get IToastNotificationManagerStatics2 "
<< std::hex << hr;
return nullptr;
}
mswr::ComPtr<winui::Notifications::IToastNotificationHistory>
notification_history;
hr = toast_manager2->get_History(¬ification_history);
if (FAILED(hr)) {
LogHistoryHistogram(HistoryStatus::kGetToastHistoryFailed);
DLOG(ERROR) << "Failed to get IToastNotificationHistory " << std::hex
<< hr;
return nullptr;
}
LogHistoryHistogram(HistoryStatus::kSuccess);
return notification_history;
}
std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>
GetDisplayedFromActionCenter() const {
mswr::ComPtr<winui::Notifications::IToastNotificationHistory> history =
GetIToastNotificationHistory();
if (!history) {
LogGetDisplayedStatus(GetDisplayedStatus::kGetToastHistoryFailed);
DLOG(ERROR) << "Failed to get IToastNotificationHistory";
return {};
}
mswr::ComPtr<winui::Notifications::IToastNotificationHistory2> history2;
HRESULT hr = history.As(&history2);
if (FAILED(hr)) {
LogGetDisplayedStatus(
GetDisplayedStatus::kQueryToastNotificationHistory2Failed);
DLOG(ERROR) << "Failed to get IToastNotificationHistory2 " << std::hex
<< hr;
return {};
}
ScopedHString application_id = ScopedHString::Create(GetAppId());
mswr::ComPtr<winfoundtn::Collections::IVectorView<
winui::Notifications::ToastNotification*>>
list;
hr = history2->GetHistoryWithId(application_id.get(), &list);
if (FAILED(hr)) {
LogGetDisplayedStatus(GetDisplayedStatus::kGetHistoryWithIdFailed);
DLOG(ERROR) << "GetHistoryWithId failed " << std::hex << hr;
return {};
}
uint32_t size;
hr = list->get_Size(&size);
if (FAILED(hr)) {
LogGetDisplayedStatus(GetDisplayedStatus::kGetSizeFailed);
DLOG(ERROR) << "History get_Size call failed " << std::hex << hr;
return {};
}
GetDisplayedStatus status = GetDisplayedStatus::kSuccess;
std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>
notifications;
for (uint32_t index = 0; index < size; ++index) {
mswr::ComPtr<winui::Notifications::IToastNotification> tn;
hr = list->GetAt(index, &tn);
if (FAILED(hr)) {
status = GetDisplayedStatus::kSuccessWithGetAtFailure;
DLOG(ERROR) << "Failed to get notification " << index << " of " << size
<< " " << std::hex << hr;
continue;
}
notifications.push_back(std::move(tn));
}
LogGetDisplayedStatus(status);
return notifications;
}
std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>
GetNotifications() const {
if (NotificationPlatformBridgeWinImpl::notifications_for_testing_)
return *NotificationPlatformBridgeWinImpl::notifications_for_testing_;
return GetDisplayedFromActionCenter();
}
void GetDisplayed(const std::string& profile_id,
bool incognito,
std::optional<GURL> origin,
GetDisplayedNotificationsCallback callback) const {
DCHECK(notification_task_runner_->RunsTasksInCurrentSequence());
std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>
notifications = GetNotifications();
std::set<std::string> displayed_notifications;
for (const auto& notification : notifications) {
NotificationLaunchId launch_id(
GetNotificationLaunchId(notification.Get()));
if (!launch_id.is_valid()) {
LogGetDisplayedLaunchIdStatus(
GetDisplayedLaunchIdStatus::kDecodeLaunchIdFailed);
DLOG(ERROR) << "Failed to decode notification ID";
continue;
}
if (launch_id.profile_id() != profile_id ||
launch_id.incognito() != incognito) {
continue;
}
if (origin.has_value() &&
!url::IsSameOriginWith(launch_id.origin_url(), *origin)) {
continue;
}
LogGetDisplayedLaunchIdStatus(GetDisplayedLaunchIdStatus::kSuccess);
displayed_notifications.insert(launch_id.notification_id());
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), std::move(displayed_notifications),
/*supports_synchronization=*/true));
}
void SynchronizeNotifications() {
DCHECK(notification_task_runner_->RunsTasksInCurrentSequence());
if (NotificationPlatformBridgeWinImpl::
expected_displayed_notifications_for_testing_) {
displayed_notifications_ =
*NotificationPlatformBridgeWinImpl::
expected_displayed_notifications_for_testing_;
}
if (displayed_notifications_.empty()) {
synchronize_displayed_notifications_timer_.Stop();
return;
}
std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>
notifications = GetNotifications();
// Store the set of notifications displayed taking the account the
// |profile_id| and |incognito|.
std::set<NotificationPlatformBridgeWin::NotificationKeyType>
displayed_notifications;
for (const auto& notification : notifications) {
NotificationLaunchId launch_id(
GetNotificationLaunchId(notification.Get()));
if (!launch_id.is_valid()) {
continue;
}
displayed_notifications.insert({launch_id.profile_id(),
launch_id.notification_id(),
launch_id.app_user_model_id()});
}
// Dispatch close event for all notifications that are not longer displayed.
std::vector<NotificationPlatformBridgeWin::NotificationKeyType>
key_to_remove;
for (const auto& notification : displayed_notifications_) {
if (!displayed_notifications.count(notification.first)) {
HandleEvent(/*launch_id=*/notification.second,
NotificationOperation::kClose,
/*action_index=*/std::nullopt, /*by_user=*/true);
key_to_remove.push_back(notification.first);
}
}
for (const auto& key : key_to_remove)
displayed_notifications_.erase(key);
}
void InitializeOnTaskRunner() {
DCHECK(notification_task_runner_->RunsTasksInCurrentSequence());
LogSettingPolicyAtStartup();
InitializeExpectedDisplayedNotification();
}
void InitializeExpectedDisplayedNotification() {
DCHECK(notification_task_runner_->RunsTasksInCurrentSequence());
std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>
notifications = GetNotifications();
for (const auto& notification : notifications) {
NotificationLaunchId launch_id(
GetNotificationLaunchId(notification.Get()));
if (!launch_id.is_valid()) {
continue;
}
displayed_notifications_[{launch_id.profile_id(),
launch_id.notification_id()}] = launch_id;
}
if (!displayed_notifications_.empty())
MaybeStartNotificationSynchronizationTimer();
}
void LogSettingPolicyAtStartup() {
DCHECK(notification_task_runner_->RunsTasksInCurrentSequence());
if (!notifier_for_testing_ && !notifier_.Get() &&
FAILED(InitializeToastNotifier(GetAppId()))) {
// A histogram should have already been logged for this failure.
DLOG(ERROR) << "Unable to initialize toast notifier";
return;
}
winui::Notifications::IToastNotifier* notifier =
notifier_for_testing_ ? notifier_for_testing_ : notifier_.Get();
winui::Notifications::NotificationSetting setting;
HRESULT hr = notifier->get_Setting(&setting);
if (SUCCEEDED(hr)) {
LogGetSettingStatusStartup(GetSettingStatus::kSuccess);
LogGetSettingPolicyStartup(ConvertSettingPolicy(setting));
} else {
LogGetSettingStatusStartup(GetSettingStatus::kUnknownFailure);
}
}
// Test to see if the notification_helper.exe has been registered in the
// system, either under HKCU or HKLM.
bool IsToastActivatorRegistered() {
base::win::RegKey key;
std::wstring path =
InstallUtil::GetToastActivatorRegistryPath() + L"\\LocalServer32";
HKEY root = install_static::IsSystemInstall() ? HKEY_LOCAL_MACHINE
: HKEY_CURRENT_USER;
return ERROR_SUCCESS == key.Open(root, path.c_str(), KEY_QUERY_VALUE);
}
void SetReadyCallback(
NotificationPlatformBridge::NotificationBridgeReadyCallback callback) {
DCHECK(notification_task_runner_->RunsTasksInCurrentSequence());
bool activator_registered = IsToastActivatorRegistered();
bool shortcut_installed =
InstallUtil::IsStartMenuShortcutWithActivatorGuidInstalled();
int status = static_cast<int>(SetReadyCallbackStatus::kSuccess);
bool enabled = activator_registered && shortcut_installed;
if (!enabled) {
if (!shortcut_installed) {
status |=
static_cast<int>(SetReadyCallbackStatus::kShortcutMisconfiguration);
}
if (!activator_registered) {
status |= static_cast<int>(
SetReadyCallbackStatus::kComServerMisconfiguration);
}
}
LogSetReadyCallbackStatus(static_cast<SetReadyCallbackStatus>(status));
bool success = content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), enabled));
DCHECK(success);
}
void HandleEvent(NotificationLaunchId launch_id,
NotificationOperation operation,
const std::optional<int>& action_index,
const std::optional<bool>& by_user) {
if (!launch_id.is_valid()) {
LogHandleEventStatus(HandleEventStatus::kHandleEventLaunchIdInvalid);
DLOG(ERROR) << "Failed to decode launch ID for operation "
<< static_cast<int>(operation);
return;
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(&ForwardNotificationOperationOnUiThread, operation,
launch_id.notification_type(), launch_id.origin_url(),
launch_id.notification_id(), launch_id.profile_id(),
launch_id.incognito(), action_index,
/*reply=*/std::nullopt, by_user));
LogHandleEventStatus(HandleEventStatus::kSuccess);
}
std::optional<int> ParseActionIndex(
winui::Notifications::IToastActivatedEventArgs* args) {
HSTRING arguments;
HRESULT hr = args->get_Arguments(&arguments);
if (FAILED(hr))
return std::nullopt;
ScopedHString arguments_scoped(arguments);
NotificationLaunchId launch_id(arguments_scoped.GetAsUTF8());
return (launch_id.is_valid() && launch_id.button_index() >= 0)
? std::optional<int>(launch_id.button_index())
: std::nullopt;
}
void ForwardHandleEventForTesting(
NotificationOperation operation,
winui::Notifications::IToastNotification* notification,
winui::Notifications::IToastActivatedEventArgs* args,
const std::optional<bool>& by_user) {
std::optional<int> action_index = ParseActionIndex(args);
HandleEvent(GetNotificationLaunchId(notification), operation, action_index,
by_user);
}
private:
friend class base::RefCountedThreadSafe<NotificationPlatformBridgeWinImpl>;
friend class NotificationPlatformBridgeWin;
~NotificationPlatformBridgeWinImpl() {
notification_task_runner_->DeleteSoon(FROM_HERE, image_retainer_.release());
}
std::wstring GetAppId() const {
return ShellUtil::GetBrowserModelId(InstallUtil::IsPerUserInstall());
}
std::wstring GetTag(const std::string& notification_id,
const std::string& profile_id,
const std::wstring& app_user_model_id,
bool incognito) {
std::string payload = base::StringPrintf(
"%s|%s|%s|%d", notification_id.c_str(), profile_id.c_str(),
base::WideToUTF8(app_user_model_id).c_str(), incognito);
return base::NumberToWString(base::Hash(payload));
}
HRESULT OnFailed(winui::Notifications::IToastNotification* notification,
winui::Notifications::IToastFailedEventArgs* arguments) {
HRESULT error_code;
HRESULT hr = arguments->get_ErrorCode(&error_code);
if (SUCCEEDED(hr)) {
// Error code successfully obtained from the Action Center.
LogOnFailedStatus(OnFailedStatus::kSuccess);
base::UmaHistogramSparse("Notifications.Windows.DisplayFailure",
error_code);
DLOG(ERROR) << "Failed to raise the toast notification, error code: "
<< std::hex << error_code;
} else {
LogOnFailedStatus(OnFailedStatus::kGetErrorCodeFailed);
DLOG(ERROR) << "Failed to raise the toast notification; failed to get "
"error code: "
<< std::hex << hr;
}
return S_OK;
}
HRESULT InitializeToastNotifier(const std::wstring& app_id) {
mswr::ComPtr<winui::Notifications::IToastNotificationManagerStatics>
toast_manager;
HRESULT hr = CreateActivationFactory(
RuntimeClass_Windows_UI_Notifications_ToastNotificationManager,
IID_PPV_ARGS(&toast_manager));
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kCreateToastNotificationManagerFailed);
DLOG(ERROR) << "Unable to create the ToastNotificationManager "
<< std::hex << hr;
return hr;
}
ScopedHString application_id = ScopedHString::Create(app_id);
hr = toast_manager->CreateToastNotifierWithId(application_id.get(),
¬ifier_);
if (FAILED(hr)) {
LogDisplayHistogram(DisplayStatus::kCreateToastNotifierWithIdFailed);
base::UmaHistogramSparse(
"Notifications.Windows.CreateToastManagerErrorCode", hr);
DLOG(ERROR) << "Unable to create the ToastNotifier " << std::hex << hr;
}
return hr;
}
void MaybeStartNotificationSynchronizationTimer() {
// Avoid touching synchronize_displayed_notifications_timer_ in testing,
// to avoid sequence checker issues at shutdown.
if (NotificationPlatformBridgeWinImpl::notifications_for_testing_)
return;
if (synchronize_displayed_notifications_timer_.IsRunning())
return;
synchronize_displayed_notifications_timer_.Start(
FROM_HERE, kSynchronizationInterval,
base::BindRepeating(
&NotificationPlatformBridgeWinImpl::SynchronizeNotifications,
this));
}
static std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>*
notifications_for_testing_;
static std::map<NotificationPlatformBridgeWin::NotificationKeyType,
NotificationLaunchId>*
expected_displayed_notifications_for_testing_;
static winui::Notifications::IToastNotifier* notifier_for_testing_;
const base::TimeDelta kSynchronizationInterval = base::Minutes(10);
// Windows does not fire a close event when the notification closes. To work
// around this, NotificationPlatformBridgeWinImpl simulates the close event by
// periodically checking the currently displayed notifications. When
// displaying a new notification, NotificationPlatformBridgeWinImpl stores the
// NotificationLaunchId in the |displayed_notifications_| map.
//
// Every |kSynchronizationInterval| minutes, NotificationPlatformBridgeWinImpl
// compares the currently displayed notifications against the
// |displayed_notifications_| map. NotificationPlatformBridgeWinImpl fires the
// close event for all notifications in |displayed_notifications_| that are no
// longer displayed.
base::RepeatingTimer synchronize_displayed_notifications_timer_;
std::map<NotificationPlatformBridgeWin::NotificationKeyType,
NotificationLaunchId>
displayed_notifications_;
// The task runner running notification related tasks.
scoped_refptr<base::SequencedTaskRunner> notification_task_runner_;
// An object that keeps temp files alive long enough for Windows to pick up.
std::unique_ptr<NotificationImageRetainer> image_retainer_;
// The ToastNotifier to use to communicate with the Action Center.
mswr::ComPtr<winui::Notifications::IToastNotifier> notifier_;
};
std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>*
NotificationPlatformBridgeWinImpl::notifications_for_testing_ = nullptr;
std::map<NotificationPlatformBridgeWin::NotificationKeyType,
NotificationLaunchId>* NotificationPlatformBridgeWinImpl::
expected_displayed_notifications_for_testing_ = nullptr;
winui::Notifications::IToastNotifier*
NotificationPlatformBridgeWinImpl::notifier_for_testing_ = nullptr;
NotificationPlatformBridgeWin::NotificationPlatformBridgeWin() {
notification_task_runner_ = base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
impl_ = base::MakeRefCounted<NotificationPlatformBridgeWinImpl>(
notification_task_runner_);
}
NotificationPlatformBridgeWin::~NotificationPlatformBridgeWin() = default;
void NotificationPlatformBridgeWin::Display(
NotificationHandler::Type notification_type,
Profile* profile,
const message_center::Notification& notification,
std::unique_ptr<NotificationCommon::Metadata> metadata) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
// Make a deep copy of the notification as its resources cannot safely
// be passed between threads.
auto notification_copy = message_center::Notification::DeepCopy(
notification,
ThemeServiceFactory::GetForProfile(profile)->GetColorProvider(),
/*include_body_image=*/true, /*include_small_image=*/true,
/*include_icon_images=*/true);
notification_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NotificationPlatformBridgeWinImpl::Display, impl_,
notification_type, GetProfileId(profile),
profile->IsOffTheRecord(), std::move(notification_copy),
std::move(metadata)));
}
void NotificationPlatformBridgeWin::Close(Profile* profile,
const std::string& notification_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
notification_task_runner_->PostTask(
FROM_HERE, base::BindOnce(&NotificationPlatformBridgeWinImpl::Close,
impl_, GetProfileId(profile),
profile->IsOffTheRecord(), notification_id));
}
void NotificationPlatformBridgeWin::GetDisplayed(
Profile* profile,
GetDisplayedNotificationsCallback callback) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
notification_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NotificationPlatformBridgeWinImpl::GetDisplayed, impl_,
GetProfileId(profile), profile->IsOffTheRecord(),
/*origin=*/std::nullopt, std::move(callback)));
}
void NotificationPlatformBridgeWin::GetDisplayedForOrigin(
Profile* profile,
const GURL& origin,
GetDisplayedNotificationsCallback callback) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
notification_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NotificationPlatformBridgeWinImpl::GetDisplayed, impl_,
GetProfileId(profile), profile->IsOffTheRecord(), origin,
std::move(callback)));
}
void NotificationPlatformBridgeWin::SetReadyCallback(
NotificationBridgeReadyCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
notification_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&NotificationPlatformBridgeWinImpl::SetReadyCallback,
impl_, std::move(callback)));
}
void NotificationPlatformBridgeWin::SynchronizeNotificationsForTesting() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
notification_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&NotificationPlatformBridgeWinImpl::SynchronizeNotifications, impl_));
}
void NotificationPlatformBridgeWin::DisplayServiceShutDown(Profile* profile) {}
// static
bool NotificationPlatformBridgeWin::HandleActivation(
const base::CommandLine& command_line) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
NotificationLaunchId launch_id(base::WideToUTF8(
command_line.GetSwitchValueNative(switches::kNotificationLaunchId)));
if (!launch_id.is_valid()) {
LogActivationStatus(ActivationStatus::kInvalidLaunchId);
return false;
}
std::optional<std::u16string> reply;
std::wstring inline_reply =
command_line.GetSwitchValueNative(switches::kNotificationInlineReply);
if (!inline_reply.empty())
reply = base::AsString16(inline_reply);
NotificationOperation operation;
if (launch_id.is_for_dismiss_button())
operation = NotificationOperation::kClose;
else if (launch_id.is_for_context_menu())
operation = NotificationOperation::kSettings;
else
operation = NotificationOperation::kClick;
std::optional<int> action_index;
if (launch_id.button_index() != -1)
action_index = launch_id.button_index();
ForwardNotificationOperationOnUiThread(
operation, launch_id.notification_type(), launch_id.origin_url(),
launch_id.notification_id(), launch_id.profile_id(),
launch_id.incognito(), std::move(action_index), reply, /*by_user=*/true);
LogActivationStatus(ActivationStatus::kSuccess);
return true;
}
// static
bool NotificationPlatformBridgeWin::SystemNotificationEnabled() {
const bool enabled =
base::FeatureList::IsEnabled(features::kNativeNotifications) &&
base::FeatureList::IsEnabled(features::kSystemNotifications);
// There was a Microsoft bug in Windows 10 prior to build 17134 (i.e.,
// Version::WIN10_RS4), causing endless loops in displaying
// notifications. It significantly amplified the memory and CPU usage.
// Therefore, we enable Windows 10 system notification only for build 17134
// and later. See crbug.com/882622 and crbug.com/878823 for more details.
return base::win::GetVersion() >= base::win::Version::WIN10_RS4 && enabled;
}
void NotificationPlatformBridgeWin::ForwardHandleEventForTesting(
NotificationOperation operation,
winui::Notifications::IToastNotification* notification,
winui::Notifications::IToastActivatedEventArgs* args,
const std::optional<bool>& by_user) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
notification_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&NotificationPlatformBridgeWinImpl::ForwardHandleEventForTesting,
impl_, operation, base::Unretained(notification),
base::Unretained(args), by_user));
}
void NotificationPlatformBridgeWin::SetDisplayedNotificationsForTesting(
std::vector<mswr::ComPtr<winui::Notifications::IToastNotification>>*
notifications) {
NotificationPlatformBridgeWinImpl::notifications_for_testing_ = notifications;
if (!notifications)
impl_->displayed_notifications_.clear();
}
void NotificationPlatformBridgeWin::SetExpectedDisplayedNotificationsForTesting(
std::map<NotificationPlatformBridgeWin::NotificationKeyType,
NotificationLaunchId>* expected_displayed_notification) {
NotificationPlatformBridgeWinImpl::
expected_displayed_notifications_for_testing_ =
expected_displayed_notification;
}
void NotificationPlatformBridgeWin::SetNotifierForTesting(
winui::Notifications::IToastNotifier* notifier) {
NotificationPlatformBridgeWinImpl::notifier_for_testing_ = notifier;
}
std::map<NotificationPlatformBridgeWin::NotificationKeyType,
NotificationLaunchId>
NotificationPlatformBridgeWin::GetExpectedDisplayedNotificationForTesting()
const {
return impl_->displayed_notifications_;
}
mswr::ComPtr<winui::Notifications::IToastNotification>
NotificationPlatformBridgeWin::GetToastNotificationForTesting(
const message_center::Notification& notification,
const std::wstring& xml_template,
const std::string& profile_id,
const std::wstring& app_user_model_id,
bool incognito) {
return impl_->GetToastNotification(notification, xml_template, profile_id,
app_user_model_id, incognito);
}