Index: chrome/browser/services/gcm/push_messaging_service_impl.cc |
diff --git a/chrome/browser/services/gcm/push_messaging_service_impl.cc b/chrome/browser/services/gcm/push_messaging_service_impl.cc |
index 6722e1136251e03c6a046a31878b02c00b33acab..4860db7cb801f619cf61391739c8ccb5a9795d68 100644 |
--- a/chrome/browser/services/gcm/push_messaging_service_impl.cc |
+++ b/chrome/browser/services/gcm/push_messaging_service_impl.cc |
@@ -4,6 +4,7 @@ |
#include "chrome/browser/services/gcm/push_messaging_service_impl.h" |
+#include <bitset> |
#include <vector> |
#include "base/bind.h" |
@@ -29,6 +30,8 @@ |
#include "components/pref_registry/pref_registry_syncable.h" |
#include "content/public/browser/browser_context.h" |
#include "content/public/browser/render_frame_host.h" |
+#include "content/public/browser/service_worker_context.h" |
+#include "content/public/browser/storage_partition.h" |
#include "content/public/browser/web_contents.h" |
#include "content/public/common/child_process_host.h" |
#include "content/public/common/content_switches.h" |
@@ -250,78 +253,136 @@ void PushMessagingServiceImpl::RequireUserVisibleUX( |
// is supported. |
int notification_count = notification_service->GetNotificationUIManager()-> |
GetAllIdsByProfileAndSourceOrigin(profile_, application_id.origin).size(); |
- if (notification_count > 0) |
- return; |
- |
- // Sites with a currently visible tab don't need to show notifications. |
+ // TODO(johnme): Hiding an existing notification should also count as a useful |
+ // user-visible action done in response to a push message - but make sure that |
+ // sending two messages in rapid succession which show then hide a |
+ // notification doesn't count. |
+ bool notification_shown = notification_count > 0; |
+ |
+ bool notification_needed = true; |
+ if (!notification_shown) { |
+ // Sites with a currently visible tab don't need to show notifications. |
#if defined(OS_ANDROID) |
- for (auto it = TabModelList::begin(); it != TabModelList::end(); ++it) { |
- Profile* profile = (*it)->GetProfile(); |
- content::WebContents* active_web_contents = |
- (*it)->GetActiveWebContents(); |
+ for (auto it = TabModelList::begin(); it != TabModelList::end(); ++it) { |
+ Profile* profile = (*it)->GetProfile(); |
+ content::WebContents* active_web_contents = |
+ (*it)->GetActiveWebContents(); |
#else |
- for (chrome::BrowserIterator it; !it.done(); it.Next()) { |
- Profile* profile = it->profile(); |
- content::WebContents* active_web_contents = |
- it->tab_strip_model()->GetActiveWebContents(); |
+ for (chrome::BrowserIterator it; !it.done(); it.Next()) { |
+ Profile* profile = it->profile(); |
+ content::WebContents* active_web_contents = |
+ it->tab_strip_model()->GetActiveWebContents(); |
#endif |
- if (!active_web_contents) |
- continue; |
- |
- // Don't leak information from other profiles. |
- if (profile != profile_) |
- continue; |
- |
- // Ignore minimized windows etc. |
- switch (active_web_contents->GetMainFrame()->GetVisibilityState()) { |
- case blink::WebPageVisibilityStateHidden: |
- case blink::WebPageVisibilityStatePrerender: |
- continue; |
- case blink::WebPageVisibilityStateVisible: |
- break; |
+ if (!active_web_contents) |
+ continue; |
+ |
+ // Don't leak information from other profiles. |
+ if (profile != profile_) |
+ continue; |
+ |
+ // Ignore minimized windows etc. |
+ switch (active_web_contents->GetMainFrame()->GetVisibilityState()) { |
+ case blink::WebPageVisibilityStateHidden: |
+ case blink::WebPageVisibilityStatePrerender: |
+ continue; |
+ case blink::WebPageVisibilityStateVisible: |
+ break; |
+ } |
+ |
+ // Use the visible URL since that's the one the user is aware of (and it |
+ // doesn't matter whether the page loaded successfully). |
+ const GURL& active_url = active_web_contents->GetVisibleURL(); |
+ |
+ // Allow https://foo.example.com Service Worker to not show notification |
+ // if an https://bar.example.com tab is visible (and hence might |
+ // conceivably be showing UI in response to the push message); but http:// |
+ // doesn't count as the Service Worker can't talk to it, even with |
+ // navigator.connect. |
+ if (application_id.origin.scheme() != active_url.scheme()) |
+ continue; |
+ if (net::registry_controlled_domains::SameDomainOrHost( |
+ application_id.origin, active_url, |
+ net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) { |
+ notification_needed = false; |
+ break; |
+ } |
} |
+ } |
- // Use the visible URL since that's the one the user is aware of (and it |
- // doesn't matter whether the page loaded successfully). |
- const GURL& active_url = active_web_contents->GetVisibleURL(); |
- |
- // Allow https://foo.example.com Service Worker to not show notification if |
- // an https://bar.example.com tab is visible (and hence might conceivably |
- // be showing UI in response to the push message); but http:// doesn't count |
- // as the Service Worker can't talk to it, even with navigator.connect. |
- if (application_id.origin.scheme() != active_url.scheme()) |
- continue; |
- if (net::registry_controlled_domains::SameDomainOrHost( |
- application_id.origin, active_url, |
- net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES)) { |
- return; |
- } |
+ // Don't track push messages that didn't show a notification but were exempt |
+ // from needing to do so. |
+ if (notification_shown || notification_needed) { |
+ content::ServiceWorkerContext* service_worker_context = |
+ content::BrowserContext::GetStoragePartitionForSite( |
+ profile_, application_id.origin)->GetServiceWorkerContext(); |
+ |
+ PushMessagingService::GetNotificationsShownByLastFewPushes( |
+ service_worker_context, application_id.service_worker_registration_id, |
+ base::Bind(&PushMessagingServiceImpl::DidGetNotificationsShown, |
+ weak_factory_.GetWeakPtr(), |
+ application_id, notification_shown, notification_needed)); |
} |
+#endif // defined(ENABLE_NOTIFICATIONS) |
+} |
- // If we haven't returned yet, the site failed to show a notification, so we |
- // will show a generic notification. See https://crbug.com/437277 |
- // TODO(johnme): The generic notification should probably automatically close |
- // itself when the next push message arrives? |
- content::PlatformNotificationData notification_data; |
- // TODO(johnme): Switch to FormatOriginForDisplay from crbug.com/402698 |
- notification_data.title = l10n_util::GetStringFUTF16( |
- IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_TITLE, |
- base::UTF8ToUTF16(application_id.origin.host())); |
- notification_data.direction = |
- content::PlatformNotificationData::NotificationDirectionLeftToRight; |
- notification_data.body = |
- l10n_util::GetStringUTF16(IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_BODY); |
- notification_data.tag = |
- base::ASCIIToUTF16(kPushMessagingForcedNotificationTag); |
- notification_data.icon = GURL(); // TODO(johnme): Better icon? |
- notification_service->DisplayPersistentNotification( |
- profile_, |
- application_id.service_worker_registration_id, |
- application_id.origin, |
- SkBitmap() /* icon */, |
- notification_data, |
- content::ChildProcessHost::kInvalidUniqueID /* render_process_id */); |
-#endif |
+static void IgnoreResult(bool unused) { |
+} |
+ |
+void PushMessagingServiceImpl::DidGetNotificationsShown( |
+ const PushMessagingApplicationId& application_id, |
+ bool notification_shown, bool notification_needed, |
+ const std::string& data, bool success, bool not_found) { |
+ content::ServiceWorkerContext* service_worker_context = |
+ content::BrowserContext::GetStoragePartitionForSite( |
+ profile_, application_id.origin)->GetServiceWorkerContext(); |
+ |
+ // We remember whether the last (up to) 10 pushes showed notifications. |
+ const size_t MISSED_NOTIFICATIONS_LENGTH = 10; |
+ // data is a string like "0001000", where '0' means shown, and '1' means |
+ // needed but not shown. We manipulate it in bitset form. |
+ std::bitset<MISSED_NOTIFICATIONS_LENGTH> missed_notifications(data); |
+ |
+ bool needed_but_not_shown = notification_needed && !notification_shown; |
+ |
+ // New entries go at the end, and old ones are shifted off the beginning once |
+ // the history length is exceeded. |
+ missed_notifications <<= 1; |
+ missed_notifications[0] = needed_but_not_shown; |
+ std::string updated_data(missed_notifications. |
+ to_string<char, std::string::traits_type, std::string::allocator_type>()); |
+ PushMessagingService::SetNotificationsShownByLastFewPushes( |
+ service_worker_context, application_id.service_worker_registration_id, |
+ application_id.origin, updated_data, |
+ base::Bind(&IgnoreResult)); // This is a heuristic; ignore failure. |
+ |
+ if (needed_but_not_shown && missed_notifications.count() >= 2) { |
+ // The site failed to show a notification when one was needed, and they have |
+ // already failed once in the previous 10 push messages, so we will show a |
+ // generic notification. See https://crbug.com/437277. |
+ // TODO(johnme): The generic notification should probably automatically |
+ // close itself when the next push message arrives? |
+ content::PlatformNotificationData notification_data; |
+ // TODO(johnme): Switch to FormatOriginForDisplay from crbug.com/402698 |
+ notification_data.title = l10n_util::GetStringFUTF16( |
+ IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_TITLE, |
+ base::UTF8ToUTF16(application_id.origin.host())); |
+ notification_data.direction = |
+ content::PlatformNotificationData::NotificationDirectionLeftToRight; |
+ notification_data.body = |
+ l10n_util::GetStringUTF16(IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_BODY); |
+ notification_data.tag = |
+ base::ASCIIToUTF16(kPushMessagingForcedNotificationTag); |
+ notification_data.icon = GURL(); // TODO(johnme): Better icon? |
+ PlatformNotificationServiceImpl* notification_service = |
+ PlatformNotificationServiceImpl::GetInstance(); |
+ notification_service->DisplayPersistentNotification( |
+ profile_, |
+ application_id.service_worker_registration_id, |
+ application_id.origin, |
+ SkBitmap() /* icon */, |
+ notification_data, |
+ content::ChildProcessHost::kInvalidUniqueID /* render_process_id */); |
+ } |
} |
void PushMessagingServiceImpl::OnMessagesDeleted(const std::string& app_id) { |