1 // Copyright 2016 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "chrome/browser/notifications/persistent_notification_handler.h"
6
7 #include "base/bind.h"
8 #include "base/callback.h"
9 #include "base/check_op.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "chrome/browser/engagement/site_engagement_service.h"
12 #include "chrome/browser/notifications/metrics/notification_metrics_logger.h"
13 #include "chrome/browser/notifications/metrics/notification_metrics_logger_factory.h"
14 #include "chrome/browser/notifications/notification_common.h"
15 #include "chrome/browser/notifications/notification_permission_context.h"
16 #include "chrome/browser/notifications/platform_notification_service_factory.h"
17 #include "chrome/browser/notifications/platform_notification_service_impl.h"
18 #include "chrome/browser/profiles/profile.h"
19 #include "components/permissions/permission_uma_util.h"
20 #include "components/permissions/permission_util.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/notification_event_dispatcher.h"
23 #include "content/public/browser/permission_controller.h"
24 #include "content/public/browser/permission_type.h"
25 #include "content/public/common/persistent_notification_status.h"
26 #include "url/gurl.h"
27
28 #if BUILDFLAG(ENABLE_BACKGROUND_MODE)
29 #include "components/keep_alive_registry/keep_alive_types.h"
30 #include "components/keep_alive_registry/scoped_keep_alive.h"
31 #endif
32
33 using content::BrowserThread;
34
35 PersistentNotificationHandler::PersistentNotificationHandler() = default;
36 PersistentNotificationHandler::~PersistentNotificationHandler() = default;
37
OnClose(Profile * profile,const GURL & origin,const std::string & notification_id,bool by_user,base::OnceClosure completed_closure)38 void PersistentNotificationHandler::OnClose(
39 Profile* profile,
40 const GURL& origin,
41 const std::string& notification_id,
42 bool by_user,
43 base::OnceClosure completed_closure) {
44 DCHECK_CURRENTLY_ON(BrowserThread::UI);
45 DCHECK(origin.is_valid());
46
47 // TODO(peter): Should we do permission checks prior to forwarding to the
48 // NotificationEventDispatcher?
49
50 // If we programatically closed this notification, don't dispatch any event.
51 if (PlatformNotificationServiceFactory::GetForProfile(profile)
52 ->WasClosedProgrammatically(notification_id)) {
53 std::move(completed_closure).Run();
54 return;
55 }
56
57 NotificationMetricsLogger* metrics_logger =
58 NotificationMetricsLoggerFactory::GetForBrowserContext(profile);
59 if (by_user)
60 metrics_logger->LogPersistentNotificationClosedByUser();
61 else
62 metrics_logger->LogPersistentNotificationClosedProgrammatically();
63
64 content::NotificationEventDispatcher::GetInstance()
65 ->DispatchNotificationCloseEvent(
66 profile, notification_id, origin, by_user,
67 base::BindOnce(&PersistentNotificationHandler::OnCloseCompleted,
68 weak_ptr_factory_.GetWeakPtr(),
69 std::move(completed_closure)));
70 }
71
OnCloseCompleted(base::OnceClosure completed_closure,content::PersistentNotificationStatus status)72 void PersistentNotificationHandler::OnCloseCompleted(
73 base::OnceClosure completed_closure,
74 content::PersistentNotificationStatus status) {
75 UMA_HISTOGRAM_ENUMERATION(
76 "Notifications.PersistentWebNotificationCloseResult", status);
77
78 std::move(completed_closure).Run();
79 }
80
OnClick(Profile * profile,const GURL & origin,const std::string & notification_id,const base::Optional<int> & action_index,const base::Optional<base::string16> & reply,base::OnceClosure completed_closure)81 void PersistentNotificationHandler::OnClick(
82 Profile* profile,
83 const GURL& origin,
84 const std::string& notification_id,
85 const base::Optional<int>& action_index,
86 const base::Optional<base::string16>& reply,
87 base::OnceClosure completed_closure) {
88 DCHECK_CURRENTLY_ON(BrowserThread::UI);
89 DCHECK(origin.is_valid());
90
91 NotificationMetricsLogger* metrics_logger =
92 NotificationMetricsLoggerFactory::GetForBrowserContext(profile);
93
94 #if BUILDFLAG(ENABLE_BACKGROUND_MODE)
95 // Ensure the browser stays alive while the event is processed. The keep alive
96 // will be reset when all click events have been acknowledged.
97 if (pending_click_dispatch_events_++ == 0) {
98 click_dispatch_keep_alive_ = std::make_unique<ScopedKeepAlive>(
99 KeepAliveOrigin::PENDING_NOTIFICATION_CLICK_EVENT,
100 KeepAliveRestartOption::DISABLED);
101 }
102 #endif
103
104 blink::mojom::PermissionStatus permission_status =
105 content::BrowserContext::GetPermissionController(profile)
106 ->GetPermissionStatus(content::PermissionType::NOTIFICATIONS, origin,
107 origin);
108
109 // Don't process click events when the |origin| doesn't have permission. This
110 // can't be a DCHECK because of potential races with native notifications.
111 if (permission_status != blink::mojom::PermissionStatus::GRANTED) {
112 metrics_logger->LogPersistentNotificationClickWithoutPermission();
113
114 OnClickCompleted(profile, notification_id, std::move(completed_closure),
115 content::PersistentNotificationStatus::kPermissionMissing);
116 return;
117 }
118
119 if (action_index.has_value())
120 metrics_logger->LogPersistentNotificationActionButtonClick();
121 else
122 metrics_logger->LogPersistentNotificationClick();
123
124 // Notification clicks are considered a form of engagement with the |origin|,
125 // thus we log the interaction with the Site Engagement service.
126 SiteEngagementService::Get(profile)->HandleNotificationInteraction(origin);
127
128 content::NotificationEventDispatcher::GetInstance()
129 ->DispatchNotificationClickEvent(
130 profile, notification_id, origin, action_index, reply,
131 base::BindOnce(&PersistentNotificationHandler::OnClickCompleted,
132 weak_ptr_factory_.GetWeakPtr(), profile,
133 notification_id, std::move(completed_closure)));
134 }
135
OnClickCompleted(Profile * profile,const std::string & notification_id,base::OnceClosure completed_closure,content::PersistentNotificationStatus status)136 void PersistentNotificationHandler::OnClickCompleted(
137 Profile* profile,
138 const std::string& notification_id,
139 base::OnceClosure completed_closure,
140 content::PersistentNotificationStatus status) {
141 UMA_HISTOGRAM_ENUMERATION(
142 "Notifications.PersistentWebNotificationClickResult", status);
143
144 switch (status) {
145 case content::PersistentNotificationStatus::kSuccess:
146 case content::PersistentNotificationStatus::kServiceWorkerError:
147 case content::PersistentNotificationStatus::kWaitUntilRejected:
148 // There either wasn't a failure, or one that's in the developer's
149 // control, so we don't act on the origin's behalf.
150 break;
151 case content::PersistentNotificationStatus::kServiceWorkerMissing:
152 case content::PersistentNotificationStatus::kDatabaseError:
153 case content::PersistentNotificationStatus::kPermissionMissing:
154 // There was a failure that's out of the developer's control. The user now
155 // observes a stuck notification, so let's close it for them.
156 PlatformNotificationServiceFactory::GetForProfile(profile)
157 ->ClosePersistentNotification(notification_id);
158 break;
159 }
160
161 #if BUILDFLAG(ENABLE_BACKGROUND_MODE)
162 DCHECK_GT(pending_click_dispatch_events_, 0);
163
164 // Reset the keep alive if all in-flight events have been processed.
165 if (--pending_click_dispatch_events_ == 0)
166 click_dispatch_keep_alive_.reset();
167 #endif
168
169 std::move(completed_closure).Run();
170 }
171
DisableNotifications(Profile * profile,const GURL & origin)172 void PersistentNotificationHandler::DisableNotifications(Profile* profile,
173 const GURL& origin) {
174 permissions::PermissionUmaUtil::ScopedRevocationReporter
175 scoped_revocation_reporter(
176 profile, origin, origin, ContentSettingsType::NOTIFICATIONS,
177 permissions::PermissionSourceUI::INLINE_SETTINGS);
178 NotificationPermissionContext::UpdatePermission(profile, origin,
179 CONTENT_SETTING_BLOCK);
180 }
181
OpenSettings(Profile * profile,const GURL & origin)182 void PersistentNotificationHandler::OpenSettings(Profile* profile,
183 const GURL& origin) {
184 NotificationCommon::OpenNotificationSettings(profile, origin);
185 }
186