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