OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 1 // Copyright 2014 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/services/gcm/push_messaging_service_impl.h" | 5 #include "chrome/browser/services/gcm/push_messaging_service_impl.h" |
6 | 6 |
| 7 #include <bitset> |
7 #include <vector> | 8 #include <vector> |
8 | 9 |
9 #include "base/bind.h" | 10 #include "base/bind.h" |
10 #include "base/command_line.h" | 11 #include "base/command_line.h" |
11 #include "base/prefs/pref_service.h" | 12 #include "base/prefs/pref_service.h" |
12 #include "base/strings/string_util.h" | 13 #include "base/strings/string_util.h" |
13 #include "base/strings/utf_string_conversions.h" | 14 #include "base/strings/utf_string_conversions.h" |
14 #include "chrome/browser/browser_process.h" | 15 #include "chrome/browser/browser_process.h" |
15 #include "chrome/browser/notifications/notification_ui_manager.h" | 16 #include "chrome/browser/notifications/notification_ui_manager.h" |
16 #include "chrome/browser/notifications/platform_notification_service_impl.h" | 17 #include "chrome/browser/notifications/platform_notification_service_impl.h" |
17 #include "chrome/browser/profiles/profile.h" | 18 #include "chrome/browser/profiles/profile.h" |
18 #include "chrome/browser/services/gcm/gcm_profile_service.h" | 19 #include "chrome/browser/services/gcm/gcm_profile_service.h" |
19 #include "chrome/browser/services/gcm/gcm_profile_service_factory.h" | 20 #include "chrome/browser/services/gcm/gcm_profile_service_factory.h" |
20 #include "chrome/browser/services/gcm/push_messaging_application_id.h" | 21 #include "chrome/browser/services/gcm/push_messaging_application_id.h" |
21 #include "chrome/browser/services/gcm/push_messaging_constants.h" | 22 #include "chrome/browser/services/gcm/push_messaging_constants.h" |
22 #include "chrome/browser/services/gcm/push_messaging_permission_context.h" | 23 #include "chrome/browser/services/gcm/push_messaging_permission_context.h" |
23 #include "chrome/browser/services/gcm/push_messaging_permission_context_factory.
h" | 24 #include "chrome/browser/services/gcm/push_messaging_permission_context_factory.
h" |
24 #include "chrome/common/chrome_switches.h" | 25 #include "chrome/common/chrome_switches.h" |
25 #include "chrome/common/pref_names.h" | 26 #include "chrome/common/pref_names.h" |
26 #include "chrome/grit/generated_resources.h" | 27 #include "chrome/grit/generated_resources.h" |
27 #include "components/content_settings/core/common/permission_request_id.h" | 28 #include "components/content_settings/core/common/permission_request_id.h" |
28 #include "components/gcm_driver/gcm_driver.h" | 29 #include "components/gcm_driver/gcm_driver.h" |
29 #include "components/pref_registry/pref_registry_syncable.h" | 30 #include "components/pref_registry/pref_registry_syncable.h" |
30 #include "content/public/browser/browser_context.h" | 31 #include "content/public/browser/browser_context.h" |
31 #include "content/public/browser/render_frame_host.h" | 32 #include "content/public/browser/render_frame_host.h" |
| 33 #include "content/public/browser/service_worker_context.h" |
| 34 #include "content/public/browser/storage_partition.h" |
32 #include "content/public/browser/web_contents.h" | 35 #include "content/public/browser/web_contents.h" |
33 #include "content/public/common/child_process_host.h" | 36 #include "content/public/common/child_process_host.h" |
34 #include "content/public/common/content_switches.h" | 37 #include "content/public/common/content_switches.h" |
35 #include "content/public/common/platform_notification_data.h" | 38 #include "content/public/common/platform_notification_data.h" |
36 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | 39 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
37 #include "third_party/skia/include/core/SkBitmap.h" | 40 #include "third_party/skia/include/core/SkBitmap.h" |
38 #include "ui/base/l10n/l10n_util.h" | 41 #include "ui/base/l10n/l10n_util.h" |
39 | 42 |
40 #if defined(OS_ANDROID) | 43 #if defined(OS_ANDROID) |
41 #include "chrome/browser/ui/android/tab_model/tab_model.h" | 44 #include "chrome/browser/ui/android/tab_model/tab_model.h" |
(...skipping 201 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
243 #if defined(ENABLE_NOTIFICATIONS) | 246 #if defined(ENABLE_NOTIFICATIONS) |
244 // TODO(johnme): Relax this heuristic slightly. | 247 // TODO(johnme): Relax this heuristic slightly. |
245 PlatformNotificationServiceImpl* notification_service = | 248 PlatformNotificationServiceImpl* notification_service = |
246 PlatformNotificationServiceImpl::GetInstance(); | 249 PlatformNotificationServiceImpl::GetInstance(); |
247 // Can't use g_browser_process->notification_ui_manager(), since the test uses | 250 // Can't use g_browser_process->notification_ui_manager(), since the test uses |
248 // PlatformNotificationServiceImpl::SetNotificationUIManagerForTesting. | 251 // PlatformNotificationServiceImpl::SetNotificationUIManagerForTesting. |
249 // TODO(peter): Remove the need to use both APIs here once Notification.get() | 252 // TODO(peter): Remove the need to use both APIs here once Notification.get() |
250 // is supported. | 253 // is supported. |
251 int notification_count = notification_service->GetNotificationUIManager()-> | 254 int notification_count = notification_service->GetNotificationUIManager()-> |
252 GetAllIdsByProfileAndSourceOrigin(profile_, application_id.origin).size(); | 255 GetAllIdsByProfileAndSourceOrigin(profile_, application_id.origin).size(); |
253 if (notification_count > 0) | 256 // TODO(johnme): Hiding an existing notification should also count as a useful |
254 return; | 257 // user-visible action done in response to a push message - but make sure that |
| 258 // sending two messages in rapid succession which show then hide a |
| 259 // notification doesn't count. |
| 260 bool notification_shown = notification_count > 0; |
255 | 261 |
256 // Sites with a currently visible tab don't need to show notifications. | 262 bool notification_needed = true; |
| 263 if (!notification_shown) { |
| 264 // Sites with a currently visible tab don't need to show notifications. |
257 #if defined(OS_ANDROID) | 265 #if defined(OS_ANDROID) |
258 for (auto it = TabModelList::begin(); it != TabModelList::end(); ++it) { | 266 for (auto it = TabModelList::begin(); it != TabModelList::end(); ++it) { |
259 Profile* profile = (*it)->GetProfile(); | 267 Profile* profile = (*it)->GetProfile(); |
260 content::WebContents* active_web_contents = | 268 content::WebContents* active_web_contents = |
261 (*it)->GetActiveWebContents(); | 269 (*it)->GetActiveWebContents(); |
262 #else | 270 #else |
263 for (chrome::BrowserIterator it; !it.done(); it.Next()) { | 271 for (chrome::BrowserIterator it; !it.done(); it.Next()) { |
264 Profile* profile = it->profile(); | 272 Profile* profile = it->profile(); |
265 content::WebContents* active_web_contents = | 273 content::WebContents* active_web_contents = |
266 it->tab_strip_model()->GetActiveWebContents(); | 274 it->tab_strip_model()->GetActiveWebContents(); |
267 #endif | 275 #endif |
268 if (!active_web_contents) | 276 if (!active_web_contents) |
269 continue; | 277 continue; |
270 | 278 |
271 // Don't leak information from other profiles. | 279 // Don't leak information from other profiles. |
272 if (profile != profile_) | 280 if (profile != profile_) |
273 continue; | 281 continue; |
274 | 282 |
275 // Ignore minimized windows etc. | 283 // Ignore minimized windows etc. |
276 switch (active_web_contents->GetMainFrame()->GetVisibilityState()) { | 284 switch (active_web_contents->GetMainFrame()->GetVisibilityState()) { |
277 case blink::WebPageVisibilityStateHidden: | 285 case blink::WebPageVisibilityStateHidden: |
278 case blink::WebPageVisibilityStatePrerender: | 286 case blink::WebPageVisibilityStatePrerender: |
279 continue; | 287 continue; |
280 case blink::WebPageVisibilityStateVisible: | 288 case blink::WebPageVisibilityStateVisible: |
281 break; | 289 break; |
282 } | 290 } |
283 | 291 |
284 // Use the visible URL since that's the one the user is aware of (and it | 292 // Use the visible URL since that's the one the user is aware of (and it |
285 // doesn't matter whether the page loaded successfully). | 293 // doesn't matter whether the page loaded successfully). |
286 const GURL& active_url = active_web_contents->GetVisibleURL(); | 294 const GURL& active_url = active_web_contents->GetVisibleURL(); |
287 | 295 |
288 // Allow https://foo.example.com Service Worker to not show notification if | 296 // Allow https://foo.example.com Service Worker to not show notification |
289 // an https://bar.example.com tab is visible (and hence might conceivably | 297 // if an https://bar.example.com tab is visible (and hence might |
290 // be showing UI in response to the push message); but http:// doesn't count | 298 // conceivably be showing UI in response to the push message); but http:// |
291 // as the Service Worker can't talk to it, even with navigator.connect. | 299 // doesn't count as the Service Worker can't talk to it, even with |
292 if (application_id.origin.scheme() != active_url.scheme()) | 300 // navigator.connect. |
293 continue; | 301 if (application_id.origin.scheme() != active_url.scheme()) |
294 if (net::registry_controlled_domains::SameDomainOrHost( | 302 continue; |
295 application_id.origin, active_url, | 303 if (net::registry_controlled_domains::SameDomainOrHost( |
296 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) { | 304 application_id.origin, active_url, |
297 return; | 305 net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) { |
| 306 notification_needed = false; |
| 307 break; |
| 308 } |
298 } | 309 } |
299 } | 310 } |
300 | 311 |
301 // If we haven't returned yet, the site failed to show a notification, so we | 312 // Don't track push messages that didn't show a notification but were exempt |
302 // will show a generic notification. See https://crbug.com/437277 | 313 // from needing to do so. |
303 // TODO(johnme): The generic notification should probably automatically close | 314 if (notification_shown || notification_needed) { |
304 // itself when the next push message arrives? | 315 content::ServiceWorkerContext* service_worker_context = |
305 content::PlatformNotificationData notification_data; | 316 content::BrowserContext::GetStoragePartitionForSite( |
306 // TODO(johnme): Switch to FormatOriginForDisplay from crbug.com/402698 | 317 profile_, application_id.origin)->GetServiceWorkerContext(); |
307 notification_data.title = l10n_util::GetStringFUTF16( | 318 |
308 IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_TITLE, | 319 PushMessagingService::GetNotificationsShownByLastFewPushes( |
309 base::UTF8ToUTF16(application_id.origin.host())); | 320 service_worker_context, application_id.service_worker_registration_id, |
310 notification_data.direction = | 321 base::Bind(&PushMessagingServiceImpl::DidGetNotificationsShown, |
311 content::PlatformNotificationData::NotificationDirectionLeftToRight; | 322 weak_factory_.GetWeakPtr(), |
312 notification_data.body = | 323 application_id, notification_shown, notification_needed)); |
313 l10n_util::GetStringUTF16(IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_BODY); | 324 } |
314 notification_data.tag = | 325 #endif // defined(ENABLE_NOTIFICATIONS) |
315 base::ASCIIToUTF16(kPushMessagingForcedNotificationTag); | 326 } |
316 notification_data.icon = GURL(); // TODO(johnme): Better icon? | 327 |
317 notification_service->DisplayPersistentNotification( | 328 static void IgnoreResult(bool unused) { |
318 profile_, | 329 } |
319 application_id.service_worker_registration_id, | 330 |
320 application_id.origin, | 331 void PushMessagingServiceImpl::DidGetNotificationsShown( |
321 SkBitmap() /* icon */, | 332 const PushMessagingApplicationId& application_id, |
322 notification_data, | 333 bool notification_shown, bool notification_needed, |
323 content::ChildProcessHost::kInvalidUniqueID /* render_process_id */); | 334 const std::string& data, bool success, bool not_found) { |
324 #endif | 335 content::ServiceWorkerContext* service_worker_context = |
| 336 content::BrowserContext::GetStoragePartitionForSite( |
| 337 profile_, application_id.origin)->GetServiceWorkerContext(); |
| 338 |
| 339 // We remember whether the last (up to) 10 pushes showed notifications. |
| 340 const size_t MISSED_NOTIFICATIONS_LENGTH = 10; |
| 341 // data is a string like "0001000", where '0' means shown, and '1' means |
| 342 // needed but not shown. We manipulate it in bitset form. |
| 343 std::bitset<MISSED_NOTIFICATIONS_LENGTH> missed_notifications(data); |
| 344 |
| 345 bool needed_but_not_shown = notification_needed && !notification_shown; |
| 346 |
| 347 // New entries go at the end, and old ones are shifted off the beginning once |
| 348 // the history length is exceeded. |
| 349 missed_notifications <<= 1; |
| 350 missed_notifications[0] = needed_but_not_shown; |
| 351 std::string updated_data(missed_notifications. |
| 352 to_string<char, std::string::traits_type, std::string::allocator_type>()); |
| 353 PushMessagingService::SetNotificationsShownByLastFewPushes( |
| 354 service_worker_context, application_id.service_worker_registration_id, |
| 355 application_id.origin, updated_data, |
| 356 base::Bind(&IgnoreResult)); // This is a heuristic; ignore failure. |
| 357 |
| 358 if (needed_but_not_shown && missed_notifications.count() >= 2) { |
| 359 // The site failed to show a notification when one was needed, and they have |
| 360 // already failed once in the previous 10 push messages, so we will show a |
| 361 // generic notification. See https://crbug.com/437277. |
| 362 // TODO(johnme): The generic notification should probably automatically |
| 363 // close itself when the next push message arrives? |
| 364 content::PlatformNotificationData notification_data; |
| 365 // TODO(johnme): Switch to FormatOriginForDisplay from crbug.com/402698 |
| 366 notification_data.title = l10n_util::GetStringFUTF16( |
| 367 IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_TITLE, |
| 368 base::UTF8ToUTF16(application_id.origin.host())); |
| 369 notification_data.direction = |
| 370 content::PlatformNotificationData::NotificationDirectionLeftToRight; |
| 371 notification_data.body = |
| 372 l10n_util::GetStringUTF16(IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_BODY); |
| 373 notification_data.tag = |
| 374 base::ASCIIToUTF16(kPushMessagingForcedNotificationTag); |
| 375 notification_data.icon = GURL(); // TODO(johnme): Better icon? |
| 376 PlatformNotificationServiceImpl* notification_service = |
| 377 PlatformNotificationServiceImpl::GetInstance(); |
| 378 notification_service->DisplayPersistentNotification( |
| 379 profile_, |
| 380 application_id.service_worker_registration_id, |
| 381 application_id.origin, |
| 382 SkBitmap() /* icon */, |
| 383 notification_data, |
| 384 content::ChildProcessHost::kInvalidUniqueID /* render_process_id */); |
| 385 } |
325 } | 386 } |
326 | 387 |
327 void PushMessagingServiceImpl::OnMessagesDeleted(const std::string& app_id) { | 388 void PushMessagingServiceImpl::OnMessagesDeleted(const std::string& app_id) { |
328 // TODO(mvanouwerkerk): Fire push error event on the Service Worker | 389 // TODO(mvanouwerkerk): Fire push error event on the Service Worker |
329 // corresponding to app_id. | 390 // corresponding to app_id. |
330 } | 391 } |
331 | 392 |
332 void PushMessagingServiceImpl::OnSendError( | 393 void PushMessagingServiceImpl::OnSendError( |
333 const std::string& app_id, | 394 const std::string& app_id, |
334 const GCMClient::SendErrorDetails& send_error_details) { | 395 const GCMClient::SendErrorDetails& send_error_details) { |
(...skipping 224 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
559 bool PushMessagingServiceImpl::HasPermission(const GURL& origin) { | 620 bool PushMessagingServiceImpl::HasPermission(const GURL& origin) { |
560 gcm::PushMessagingPermissionContext* permission_context = | 621 gcm::PushMessagingPermissionContext* permission_context = |
561 gcm::PushMessagingPermissionContextFactory::GetForProfile(profile_); | 622 gcm::PushMessagingPermissionContextFactory::GetForProfile(profile_); |
562 DCHECK(permission_context); | 623 DCHECK(permission_context); |
563 | 624 |
564 return permission_context->GetPermissionStatus(origin, origin) == | 625 return permission_context->GetPermissionStatus(origin, origin) == |
565 CONTENT_SETTING_ALLOW; | 626 CONTENT_SETTING_ALLOW; |
566 } | 627 } |
567 | 628 |
568 } // namespace gcm | 629 } // namespace gcm |
OLD | NEW |