1 // Copyright 2015 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.
5 #include "chrome/browser/push_messaging/push_messaging_notification_manager.h"
9 #include "base/metrics/histogram_macros.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/browser_process.h"
12 #include "chrome/browser/notifications/platform_notification_service_impl.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/push_messaging/push_messaging_constants.h"
15 #include "chrome/grit/generated_resources.h"
16 #include "components/rappor/rappor_utils.h"
17 #include "content/public/browser/browser_context.h"
18 #include "content/public/browser/browser_thread.h"
19 #include "content/public/browser/platform_notification_context.h"
20 #include "content/public/browser/push_messaging_service.h"
21 #include "content/public/browser/render_frame_host.h"
22 #include "content/public/browser/storage_partition.h"
23 #include "content/public/browser/web_contents.h"
24 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
25 #include "third_party/skia/include/core/SkBitmap.h"
26 #include "ui/base/l10n/l10n_util.h"
29 #if defined(OS_ANDROID)
30 #include "chrome/browser/ui/android/tab_model/tab_model.h"
31 #include "chrome/browser/ui/android/tab_model/tab_model_list.h"
33 #include "chrome/browser/ui/browser.h"
34 #include "chrome/browser/ui/browser_iterator.h"
35 #include "chrome/browser/ui/tabs/tab_strip_model.h"
38 using content::BrowserThread
;
42 void RecordUserVisibleStatus(content::PushUserVisibleStatus status
) {
43 UMA_HISTOGRAM_ENUMERATION("PushMessaging.UserVisibleStatus",
45 content::PUSH_USER_VISIBLE_STATUS_LAST
+ 1);
50 PushMessagingNotificationManager::PushMessagingNotificationManager(
53 weak_factory_(this) {}
55 PushMessagingNotificationManager::~PushMessagingNotificationManager() {}
57 void PushMessagingNotificationManager::EnforceUserVisibleOnlyRequirements(
58 const GURL
& requesting_origin
, int64_t service_worker_registration_id
,
59 const base::Closure
& message_handled_closure
) {
60 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
61 // TODO(johnme): Relax this heuristic slightly.
62 scoped_refptr
<content::PlatformNotificationContext
> notification_context
=
63 content::BrowserContext::GetStoragePartitionForSite(
64 profile_
, requesting_origin
)->GetPlatformNotificationContext();
65 BrowserThread::PostTask(
66 BrowserThread::IO
, FROM_HERE
,
68 &content::PlatformNotificationContext
69 ::ReadAllNotificationDataForServiceWorkerRegistration
,
71 requesting_origin
, service_worker_registration_id
,
73 &PushMessagingNotificationManager
74 ::DidGetNotificationsFromDatabaseIOProxy
,
75 weak_factory_
.GetWeakPtr(),
76 requesting_origin
, service_worker_registration_id
,
77 message_handled_closure
)));
81 void PushMessagingNotificationManager::DidGetNotificationsFromDatabaseIOProxy(
82 const base::WeakPtr
<PushMessagingNotificationManager
>& ui_weak_ptr
,
83 const GURL
& requesting_origin
,
84 int64_t service_worker_registration_id
,
85 const base::Closure
& message_handled_closure
,
87 const std::vector
<content::NotificationDatabaseData
>& data
) {
88 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
89 BrowserThread::PostTask(
90 BrowserThread::UI
, FROM_HERE
,
91 base::Bind(&PushMessagingNotificationManager
92 ::DidGetNotificationsFromDatabase
,
94 requesting_origin
, service_worker_registration_id
,
95 message_handled_closure
,
99 void PushMessagingNotificationManager::DidGetNotificationsFromDatabase(
100 const GURL
& requesting_origin
, int64_t service_worker_registration_id
,
101 const base::Closure
& message_handled_closure
,
102 bool success
, const std::vector
<content::NotificationDatabaseData
>& data
) {
103 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
104 // TODO(johnme): Hiding an existing notification should also count as a useful
105 // user-visible action done in response to a push message - but make sure that
106 // sending two messages in rapid succession which show then hide a
107 // notification doesn't count.
108 int notification_count
= success
? data
.size() : 0;
109 bool notification_shown
= notification_count
> 0;
111 bool notification_needed
= true;
112 // Sites with a currently visible tab don't need to show notifications.
113 #if defined(OS_ANDROID)
114 for (auto it
= TabModelList::begin(); it
!= TabModelList::end(); ++it
) {
115 Profile
* profile
= (*it
)->GetProfile();
116 content::WebContents
* active_web_contents
=
117 (*it
)->GetActiveWebContents();
119 for (chrome::BrowserIterator it
; !it
.done(); it
.Next()) {
120 Profile
* profile
= it
->profile();
121 content::WebContents
* active_web_contents
=
122 it
->tab_strip_model()->GetActiveWebContents();
124 if (!active_web_contents
|| !active_web_contents
->GetMainFrame())
127 // Don't leak information from other profiles.
128 if (profile
!= profile_
)
131 // Ignore minimized windows etc.
132 switch (active_web_contents
->GetMainFrame()->GetVisibilityState()) {
133 case blink::WebPageVisibilityStateHidden
:
134 case blink::WebPageVisibilityStatePrerender
:
136 case blink::WebPageVisibilityStateVisible
:
140 // Use the visible URL since that's the one the user is aware of (and it
141 // doesn't matter whether the page loaded successfully).
142 const GURL
& active_url
= active_web_contents
->GetVisibleURL();
143 if (requesting_origin
== active_url
.GetOrigin()) {
144 notification_needed
= false;
147 #if defined(OS_ANDROID)
153 // If more than two notifications are showing for this Service Worker, close
154 // the default notification if it happens to be part of this group.
155 if (notification_count
>= 2) {
156 for (const auto& notification_database_data
: data
) {
157 if (notification_database_data
.notification_data
.tag
!=
158 kPushMessagingForcedNotificationTag
)
161 PlatformNotificationServiceImpl
* platform_notification_service
=
162 PlatformNotificationServiceImpl::GetInstance();
164 // First close the notification for the user's point of view, and then
165 // manually tell the service that the notification has been closed in
166 // order to avoid duplicating the thread-jump logic.
167 platform_notification_service
->ClosePersistentNotification(
168 profile_
, notification_database_data
.notification_id
);
169 platform_notification_service
->OnPersistentNotificationClose(
170 profile_
, notification_database_data
.notification_id
,
171 notification_database_data
.origin
);
177 // Don't track push messages that didn't show a notification but were exempt
178 // from needing to do so.
179 if (notification_shown
|| notification_needed
) {
180 content::ServiceWorkerContext
* service_worker_context
=
181 content::BrowserContext::GetStoragePartitionForSite(
182 profile_
, requesting_origin
)->GetServiceWorkerContext();
184 content::PushMessagingService::GetNotificationsShownByLastFewPushes(
185 service_worker_context
, service_worker_registration_id
,
186 base::Bind(&PushMessagingNotificationManager
187 ::DidGetNotificationsShownAndNeeded
,
188 weak_factory_
.GetWeakPtr(),
189 requesting_origin
, service_worker_registration_id
,
190 notification_shown
, notification_needed
,
191 message_handled_closure
));
193 RecordUserVisibleStatus(
194 content::PUSH_USER_VISIBLE_STATUS_NOT_REQUIRED_AND_NOT_SHOWN
);
195 message_handled_closure
.Run();
199 static void IgnoreResult(bool unused
) {
202 void PushMessagingNotificationManager::DidGetNotificationsShownAndNeeded(
203 const GURL
& requesting_origin
, int64_t service_worker_registration_id
,
204 bool notification_shown
, bool notification_needed
,
205 const base::Closure
& message_handled_closure
,
206 const std::string
& data
, bool success
, bool not_found
) {
207 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
208 content::ServiceWorkerContext
* service_worker_context
=
209 content::BrowserContext::GetStoragePartitionForSite(
210 profile_
, requesting_origin
)->GetServiceWorkerContext();
212 // We remember whether the last (up to) 10 pushes showed notifications.
213 const size_t MISSED_NOTIFICATIONS_LENGTH
= 10;
214 // data is a string like "0001000", where '0' means shown, and '1' means
215 // needed but not shown. We manipulate it in bitset form.
216 std::bitset
<MISSED_NOTIFICATIONS_LENGTH
> missed_notifications(data
);
218 DCHECK(notification_shown
|| notification_needed
); // Caller must ensure this
219 bool needed_but_not_shown
= notification_needed
&& !notification_shown
;
221 // New entries go at the end, and old ones are shifted off the beginning once
222 // the history length is exceeded.
223 missed_notifications
<<= 1;
224 missed_notifications
[0] = needed_but_not_shown
;
225 std::string
updated_data(missed_notifications
.
226 to_string
<char, std::string::traits_type
, std::string::allocator_type
>());
227 content::PushMessagingService::SetNotificationsShownByLastFewPushes(
228 service_worker_context
, service_worker_registration_id
,
229 requesting_origin
, updated_data
,
230 base::Bind(&IgnoreResult
)); // This is a heuristic; ignore failure.
232 if (notification_shown
) {
233 RecordUserVisibleStatus(
235 ? content::PUSH_USER_VISIBLE_STATUS_REQUIRED_AND_SHOWN
236 : content::PUSH_USER_VISIBLE_STATUS_NOT_REQUIRED_BUT_SHOWN
);
237 message_handled_closure
.Run();
240 DCHECK(needed_but_not_shown
);
241 if (missed_notifications
.count() <= 1) { // Apply grace.
242 RecordUserVisibleStatus(
243 content::PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_USED_GRACE
);
244 message_handled_closure
.Run();
247 RecordUserVisibleStatus(
249 PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED
);
250 rappor::SampleDomainAndRegistryFromGURL(
251 g_browser_process
->rappor_service(),
252 "PushMessaging.GenericNotificationShown.Origin",
254 // The site failed to show a notification when one was needed, and they have
255 // already failed once in the previous 10 push messages, so we will show a
256 // generic notification. See https://crbug.com/437277.
257 // TODO(johnme): The generic notification should probably automatically
258 // close itself when the next push message arrives?
259 content::PlatformNotificationData notification_data
;
260 notification_data
.title
=
261 base::UTF8ToUTF16(net::registry_controlled_domains::GetDomainAndRegistry(
262 requesting_origin
.host(),
263 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES
));
264 notification_data
.direction
=
265 content::PlatformNotificationData::NotificationDirectionLeftToRight
;
266 notification_data
.body
=
267 l10n_util::GetStringUTF16(IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_BODY
);
268 notification_data
.tag
= kPushMessagingForcedNotificationTag
;
269 notification_data
.icon
= GURL();
270 notification_data
.silent
= true;
272 content::NotificationDatabaseData database_data
;
273 database_data
.origin
= requesting_origin
;
274 database_data
.service_worker_registration_id
=
275 service_worker_registration_id
;
276 database_data
.notification_data
= notification_data
;
278 scoped_refptr
<content::PlatformNotificationContext
> notification_context
=
279 content::BrowserContext::GetStoragePartitionForSite(
280 profile_
, requesting_origin
)->GetPlatformNotificationContext();
281 BrowserThread::PostTask(
282 BrowserThread::IO
, FROM_HERE
,
284 &content::PlatformNotificationContext::WriteNotificationData
,
285 notification_context
,
286 requesting_origin
, database_data
,
287 base::Bind(&PushMessagingNotificationManager
288 ::DidWriteNotificationDataIOProxy
,
289 weak_factory_
.GetWeakPtr(),
290 requesting_origin
, notification_data
,
291 message_handled_closure
)));
295 void PushMessagingNotificationManager::DidWriteNotificationDataIOProxy(
296 const base::WeakPtr
<PushMessagingNotificationManager
>& ui_weak_ptr
,
297 const GURL
& requesting_origin
,
298 const content::PlatformNotificationData
& notification_data
,
299 const base::Closure
& message_handled_closure
,
301 int64_t persistent_notification_id
) {
302 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
303 BrowserThread::PostTask(
304 BrowserThread::UI
, FROM_HERE
,
305 base::Bind(&PushMessagingNotificationManager::DidWriteNotificationData
,
307 requesting_origin
, notification_data
, message_handled_closure
,
308 success
, persistent_notification_id
));
311 void PushMessagingNotificationManager::DidWriteNotificationData(
312 const GURL
& requesting_origin
,
313 const content::PlatformNotificationData
& notification_data
,
314 const base::Closure
& message_handled_closure
,
316 int64_t persistent_notification_id
) {
317 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
319 DLOG(ERROR
) << "Writing forced notification to database should not fail";
320 message_handled_closure
.Run();
323 PlatformNotificationServiceImpl::GetInstance()->DisplayPersistentNotification(
325 persistent_notification_id
,
327 SkBitmap() /* icon */,
329 message_handled_closure
.Run();