Popular sites on the NTP: check that experiment group StartsWith (rather than IS...
[chromium-blink-merge.git] / chrome / browser / push_messaging / push_messaging_notification_manager.cc
blob7e7cc92fdf0b307867dd6bb5fa683631c7250a54
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"
7 #include <bitset>
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"
27 #include "url/gurl.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"
32 #else
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"
36 #endif
38 using content::BrowserThread;
40 namespace {
42 void RecordUserVisibleStatus(content::PushUserVisibleStatus status) {
43 UMA_HISTOGRAM_ENUMERATION("PushMessaging.UserVisibleStatus",
44 status,
45 content::PUSH_USER_VISIBLE_STATUS_LAST + 1);
48 } // namespace
50 PushMessagingNotificationManager::PushMessagingNotificationManager(
51 Profile* profile)
52 : profile_(profile),
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,
67 base::Bind(
68 &content::PlatformNotificationContext
69 ::ReadAllNotificationDataForServiceWorkerRegistration,
70 notification_context,
71 requesting_origin, service_worker_registration_id,
72 base::Bind(
73 &PushMessagingNotificationManager
74 ::DidGetNotificationsFromDatabaseIOProxy,
75 weak_factory_.GetWeakPtr(),
76 requesting_origin, service_worker_registration_id,
77 message_handled_closure)));
80 // static
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,
86 bool success,
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,
93 ui_weak_ptr,
94 requesting_origin, service_worker_registration_id,
95 message_handled_closure,
96 success, data));
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();
118 #else
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();
123 #endif
124 if (!active_web_contents || !active_web_contents->GetMainFrame())
125 continue;
127 // Don't leak information from other profiles.
128 if (profile != profile_)
129 continue;
131 // Ignore minimized windows etc.
132 switch (active_web_contents->GetMainFrame()->GetVisibilityState()) {
133 case blink::WebPageVisibilityStateHidden:
134 case blink::WebPageVisibilityStatePrerender:
135 continue;
136 case blink::WebPageVisibilityStateVisible:
137 break;
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;
145 break;
147 #if defined(OS_ANDROID)
149 #else
151 #endif
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)
159 continue;
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);
173 break;
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));
192 } else {
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(
234 notification_needed
235 ? content::PUSH_USER_VISIBLE_STATUS_REQUIRED_AND_SHOWN
236 : content::PUSH_USER_VISIBLE_STATUS_NOT_REQUIRED_BUT_SHOWN);
237 message_handled_closure.Run();
238 return;
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();
245 return;
247 RecordUserVisibleStatus(
248 content::
249 PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED);
250 rappor::SampleDomainAndRegistryFromGURL(
251 g_browser_process->rappor_service(),
252 "PushMessaging.GenericNotificationShown.Origin",
253 requesting_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::DIRECTION_LEFT_TO_RIGHT;
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,
283 base::Bind(
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)));
294 // static
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,
300 bool success,
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,
306 ui_weak_ptr,
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,
315 bool success,
316 int64_t persistent_notification_id) {
317 DCHECK_CURRENTLY_ON(BrowserThread::UI);
318 if (!success) {
319 DLOG(ERROR) << "Writing forced notification to database should not fail";
320 message_handled_closure.Run();
321 return;
323 PlatformNotificationServiceImpl::GetInstance()->DisplayPersistentNotification(
324 profile_,
325 persistent_notification_id,
326 requesting_origin,
327 SkBitmap() /* icon */,
328 notification_data);
329 message_handled_closure.Run();