OLD | NEW |
| (Empty) |
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 | |
3 // found in the LICENSE file. | |
4 | |
5 #include "chrome/browser/services/gcm/push_messaging_service_impl.h" | |
6 | |
7 #include <bitset> | |
8 #include <vector> | |
9 | |
10 #include "base/bind.h" | |
11 #include "base/command_line.h" | |
12 #include "base/logging.h" | |
13 #include "base/metrics/histogram.h" | |
14 #include "base/prefs/pref_service.h" | |
15 #include "base/strings/string_util.h" | |
16 #include "base/strings/utf_string_conversions.h" | |
17 #include "chrome/browser/browser_process.h" | |
18 #include "chrome/browser/notifications/notification_ui_manager.h" | |
19 #include "chrome/browser/notifications/platform_notification_service_impl.h" | |
20 #include "chrome/browser/profiles/profile.h" | |
21 #include "chrome/browser/services/gcm/gcm_profile_service.h" | |
22 #include "chrome/browser/services/gcm/gcm_profile_service_factory.h" | |
23 #include "chrome/browser/services/gcm/push_messaging_application_id.h" | |
24 #include "chrome/browser/services/gcm/push_messaging_constants.h" | |
25 #include "chrome/browser/services/gcm/push_messaging_permission_context.h" | |
26 #include "chrome/browser/services/gcm/push_messaging_permission_context_factory.
h" | |
27 #include "chrome/common/chrome_switches.h" | |
28 #include "chrome/common/pref_names.h" | |
29 #include "chrome/grit/generated_resources.h" | |
30 #include "components/content_settings/core/browser/host_content_settings_map.h" | |
31 #include "components/content_settings/core/common/permission_request_id.h" | |
32 #include "components/gcm_driver/gcm_driver.h" | |
33 #include "components/pref_registry/pref_registry_syncable.h" | |
34 #include "content/public/browser/browser_context.h" | |
35 #include "content/public/browser/browser_thread.h" | |
36 #include "content/public/browser/render_frame_host.h" | |
37 #include "content/public/browser/service_worker_context.h" | |
38 #include "content/public/browser/storage_partition.h" | |
39 #include "content/public/browser/web_contents.h" | |
40 #include "content/public/common/child_process_host.h" | |
41 #include "content/public/common/content_switches.h" | |
42 #include "content/public/common/platform_notification_data.h" | |
43 #include "content/public/common/push_messaging_status.h" | |
44 #include "third_party/skia/include/core/SkBitmap.h" | |
45 #include "ui/base/l10n/l10n_util.h" | |
46 | |
47 #if defined(OS_ANDROID) | |
48 #include "chrome/browser/ui/android/tab_model/tab_model.h" | |
49 #include "chrome/browser/ui/android/tab_model/tab_model_list.h" | |
50 #else | |
51 #include "chrome/browser/ui/browser.h" | |
52 #include "chrome/browser/ui/browser_iterator.h" | |
53 #include "chrome/browser/ui/tabs/tab_strip_model.h" | |
54 #endif | |
55 | |
56 namespace gcm { | |
57 | |
58 namespace { | |
59 const int kMaxRegistrations = 1000000; | |
60 | |
61 void RecordDeliveryStatus(content::PushDeliveryStatus status) { | |
62 UMA_HISTOGRAM_ENUMERATION("PushMessaging.DeliveryStatus", | |
63 status, | |
64 content::PUSH_DELIVERY_STATUS_LAST + 1); | |
65 } | |
66 | |
67 void RecordUserVisibleStatus(content::PushUserVisibleStatus status) { | |
68 UMA_HISTOGRAM_ENUMERATION("PushMessaging.UserVisibleStatus", | |
69 status, | |
70 content::PUSH_USER_VISIBLE_STATUS_LAST + 1); | |
71 } | |
72 | |
73 blink::WebPushPermissionStatus ToPushPermission(ContentSetting setting) { | |
74 switch (setting) { | |
75 case CONTENT_SETTING_ALLOW: | |
76 return blink::WebPushPermissionStatusGranted; | |
77 case CONTENT_SETTING_BLOCK: | |
78 return blink::WebPushPermissionStatusDenied; | |
79 case CONTENT_SETTING_ASK: | |
80 return blink::WebPushPermissionStatusDefault; | |
81 default: | |
82 NOTREACHED(); | |
83 return blink::WebPushPermissionStatusDenied; | |
84 } | |
85 } | |
86 | |
87 } // namespace | |
88 | |
89 // static | |
90 void PushMessagingServiceImpl::RegisterProfilePrefs( | |
91 user_prefs::PrefRegistrySyncable* registry) { | |
92 registry->RegisterIntegerPref( | |
93 prefs::kPushMessagingRegistrationCount, | |
94 0, | |
95 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); | |
96 PushMessagingApplicationId::RegisterProfilePrefs(registry); | |
97 } | |
98 | |
99 // static | |
100 void PushMessagingServiceImpl::InitializeForProfile(Profile* profile) { | |
101 // TODO(johnme): Consider whether push should be enabled in incognito. | |
102 if (!profile || profile->IsOffTheRecord()) | |
103 return; | |
104 | |
105 // TODO(johnme): If push becomes enabled in incognito (and this still uses a | |
106 // pref), be careful that this pref is read from the right profile, as prefs | |
107 // defined in a regular profile are visible in the corresponding incognito | |
108 // profile unless overridden. | |
109 // TODO(johnme): Make sure this pref doesn't get out of sync after crashes. | |
110 int count = profile->GetPrefs()->GetInteger( | |
111 prefs::kPushMessagingRegistrationCount); | |
112 if (count <= 0) | |
113 return; | |
114 | |
115 // Create the GCMProfileService, and hence instantiate this class. | |
116 GCMProfileService* gcm_service = | |
117 GCMProfileServiceFactory::GetForProfile(profile); | |
118 PushMessagingServiceImpl* push_service = | |
119 static_cast<PushMessagingServiceImpl*>( | |
120 gcm_service->push_messaging_service()); | |
121 | |
122 push_service->IncreasePushRegistrationCount(count, false /* is_pending */); | |
123 } | |
124 | |
125 PushMessagingServiceImpl::PushMessagingServiceImpl( | |
126 GCMProfileService* gcm_profile_service, | |
127 Profile* profile) | |
128 : gcm_profile_service_(gcm_profile_service), | |
129 profile_(profile), | |
130 push_registration_count_(0), | |
131 pending_push_registration_count_(0), | |
132 weak_factory_(this) { | |
133 // In some tests, we might end up with |profile_| being null at this point. | |
134 // When that is the case |profile_| will be set in SetProfileForTesting(), at | |
135 // which point the service will start to observe HostContentSettingsMap. | |
136 if (profile_) | |
137 profile_->GetHostContentSettingsMap()->AddObserver(this); | |
138 } | |
139 | |
140 PushMessagingServiceImpl::~PushMessagingServiceImpl() { | |
141 // TODO(johnme): If it's possible for this to be destroyed before GCMDriver, | |
142 // then we should call RemoveAppHandler. | |
143 profile_->GetHostContentSettingsMap()->RemoveObserver(this); | |
144 } | |
145 | |
146 void PushMessagingServiceImpl::IncreasePushRegistrationCount(int add, | |
147 bool is_pending) { | |
148 DCHECK(add > 0); | |
149 if (push_registration_count_ + pending_push_registration_count_ == 0) { | |
150 gcm_profile_service_->driver()->AddAppHandler( | |
151 kPushMessagingApplicationIdPrefix, this); | |
152 } | |
153 if (is_pending) { | |
154 pending_push_registration_count_ += add; | |
155 } else { | |
156 push_registration_count_ += add; | |
157 profile_->GetPrefs()->SetInteger(prefs::kPushMessagingRegistrationCount, | |
158 push_registration_count_); | |
159 } | |
160 } | |
161 | |
162 void PushMessagingServiceImpl::DecreasePushRegistrationCount(int subtract, | |
163 bool was_pending) { | |
164 DCHECK(subtract > 0); | |
165 if (was_pending) { | |
166 pending_push_registration_count_ -= subtract; | |
167 DCHECK(pending_push_registration_count_ >= 0); | |
168 } else { | |
169 push_registration_count_ -= subtract; | |
170 DCHECK(push_registration_count_ >= 0); | |
171 profile_->GetPrefs()->SetInteger(prefs::kPushMessagingRegistrationCount, | |
172 push_registration_count_); | |
173 } | |
174 if (push_registration_count_ + pending_push_registration_count_ == 0) { | |
175 gcm_profile_service_->driver()->RemoveAppHandler( | |
176 kPushMessagingApplicationIdPrefix); | |
177 } | |
178 } | |
179 | |
180 bool PushMessagingServiceImpl::CanHandle(const std::string& app_id) const { | |
181 return PushMessagingApplicationId::Get(profile_, app_id).IsValid(); | |
182 } | |
183 | |
184 void PushMessagingServiceImpl::ShutdownHandler() { | |
185 // TODO(johnme): Do any necessary cleanup. | |
186 } | |
187 | |
188 // OnMessage methods ----------------------------------------------------------- | |
189 | |
190 void PushMessagingServiceImpl::OnMessage( | |
191 const std::string& app_id, | |
192 const GCMClient::IncomingMessage& message) { | |
193 PushMessagingApplicationId application_id = | |
194 PushMessagingApplicationId::Get(profile_, app_id); | |
195 // Drop message and unregister if app id was unknown (maybe recently deleted). | |
196 if (!application_id.IsValid()) { | |
197 DeliverMessageCallback(app_id, GURL::EmptyGURL(), -1, message, | |
198 content::PUSH_DELIVERY_STATUS_UNKNOWN_APP_ID); | |
199 return; | |
200 } | |
201 // Drop message and unregister if |origin| has lost push permission. | |
202 if (!HasPermission(application_id.origin())) { | |
203 DeliverMessageCallback(app_id, application_id.origin(), | |
204 application_id.service_worker_registration_id(), | |
205 message, | |
206 content::PUSH_DELIVERY_STATUS_PERMISSION_DENIED); | |
207 return; | |
208 } | |
209 | |
210 // The Push API only exposes a single string of data in the push event fired | |
211 // on the Service Worker. When developers send messages using GCM to the Push | |
212 // API and want to include a message payload, they must pass a single key- | |
213 // value pair, where the key is "data" and the value is the string they want | |
214 // to be passed to their Service Worker. For example, they could send the | |
215 // following JSON using the HTTPS GCM API: | |
216 // { | |
217 // "registration_ids": ["FOO", "BAR"], | |
218 // "data": { | |
219 // "data": "BAZ", | |
220 // }, | |
221 // "delay_while_idle": true, | |
222 // } | |
223 // TODO(johnme): Make sure this is clearly documented for developers. | |
224 std::string data; | |
225 // TODO(peter): Message payloads are disabled pending mandatory encryption. | |
226 // https://crbug.com/449184 | |
227 if (base::CommandLine::ForCurrentProcess()->HasSwitch( | |
228 switches::kEnablePushMessagePayload)) { | |
229 GCMClient::MessageData::const_iterator it = message.data.find("data"); | |
230 if (it != message.data.end()) | |
231 data = it->second; | |
232 } | |
233 | |
234 content::BrowserContext::DeliverPushMessage( | |
235 profile_, | |
236 application_id.origin(), | |
237 application_id.service_worker_registration_id(), | |
238 data, | |
239 base::Bind(&PushMessagingServiceImpl::DeliverMessageCallback, | |
240 weak_factory_.GetWeakPtr(), | |
241 application_id.app_id_guid(), application_id.origin(), | |
242 application_id.service_worker_registration_id(), message)); | |
243 } | |
244 | |
245 void PushMessagingServiceImpl::DeliverMessageCallback( | |
246 const std::string& app_id_guid, | |
247 const GURL& requesting_origin, | |
248 int64 service_worker_registration_id, | |
249 const GCMClient::IncomingMessage& message, | |
250 content::PushDeliveryStatus status) { | |
251 // TODO(mvanouwerkerk): Show a warning in the developer console of the | |
252 // Service Worker corresponding to app_id (and/or on an internals page). | |
253 // TODO(mvanouwerkerk): Is there a way to recover from failure? | |
254 switch (status) { | |
255 // Call RequireUserVisibleUX if the message was delivered to the Service | |
256 // Worker JS, even if the website's event handler failed (to prevent sites | |
257 // deliberately failing in order to avoid having to show notifications). | |
258 case content::PUSH_DELIVERY_STATUS_SUCCESS: | |
259 case content::PUSH_DELIVERY_STATUS_EVENT_WAITUNTIL_REJECTED: | |
260 RequireUserVisibleUX(requesting_origin, service_worker_registration_id); | |
261 break; | |
262 case content::PUSH_DELIVERY_STATUS_INVALID_MESSAGE: | |
263 case content::PUSH_DELIVERY_STATUS_SERVICE_WORKER_ERROR: | |
264 break; | |
265 case content::PUSH_DELIVERY_STATUS_UNKNOWN_APP_ID: | |
266 case content::PUSH_DELIVERY_STATUS_PERMISSION_DENIED: | |
267 case content::PUSH_DELIVERY_STATUS_NO_SERVICE_WORKER: | |
268 Unregister(app_id_guid, message.sender_id, true /* retry_on_failure */, | |
269 UnregisterCallback()); | |
270 break; | |
271 } | |
272 RecordDeliveryStatus(status); | |
273 } | |
274 | |
275 void PushMessagingServiceImpl::RequireUserVisibleUX( | |
276 const GURL& requesting_origin, int64 service_worker_registration_id) { | |
277 #if defined(ENABLE_NOTIFICATIONS) | |
278 // TODO(johnme): Relax this heuristic slightly. | |
279 PlatformNotificationServiceImpl* notification_service = | |
280 PlatformNotificationServiceImpl::GetInstance(); | |
281 // Can't use g_browser_process->notification_ui_manager(), since the test uses | |
282 // PlatformNotificationServiceImpl::SetNotificationUIManagerForTesting. | |
283 // TODO(peter): Remove the need to use both APIs here once Notification.get() | |
284 // is supported. | |
285 int notification_count = notification_service->GetNotificationUIManager()-> | |
286 GetAllIdsByProfileAndSourceOrigin(profile_, requesting_origin).size(); | |
287 // TODO(johnme): Hiding an existing notification should also count as a useful | |
288 // user-visible action done in response to a push message - but make sure that | |
289 // sending two messages in rapid succession which show then hide a | |
290 // notification doesn't count. | |
291 bool notification_shown = notification_count > 0; | |
292 | |
293 bool notification_needed = true; | |
294 // Sites with a currently visible tab don't need to show notifications. | |
295 #if defined(OS_ANDROID) | |
296 for (auto it = TabModelList::begin(); it != TabModelList::end(); ++it) { | |
297 Profile* profile = (*it)->GetProfile(); | |
298 content::WebContents* active_web_contents = | |
299 (*it)->GetActiveWebContents(); | |
300 #else | |
301 for (chrome::BrowserIterator it; !it.done(); it.Next()) { | |
302 Profile* profile = it->profile(); | |
303 content::WebContents* active_web_contents = | |
304 it->tab_strip_model()->GetActiveWebContents(); | |
305 #endif | |
306 if (!active_web_contents || !active_web_contents->GetMainFrame()) | |
307 continue; | |
308 | |
309 // Don't leak information from other profiles. | |
310 if (profile != profile_) | |
311 continue; | |
312 | |
313 // Ignore minimized windows etc. | |
314 switch (active_web_contents->GetMainFrame()->GetVisibilityState()) { | |
315 case blink::WebPageVisibilityStateHidden: | |
316 case blink::WebPageVisibilityStatePrerender: | |
317 continue; | |
318 case blink::WebPageVisibilityStateVisible: | |
319 break; | |
320 } | |
321 | |
322 // Use the visible URL since that's the one the user is aware of (and it | |
323 // doesn't matter whether the page loaded successfully). | |
324 const GURL& active_url = active_web_contents->GetVisibleURL(); | |
325 if (requesting_origin == active_url.GetOrigin()) { | |
326 notification_needed = false; | |
327 break; | |
328 } | |
329 #if defined(OS_ANDROID) | |
330 } | |
331 #else | |
332 } | |
333 #endif | |
334 | |
335 // Don't track push messages that didn't show a notification but were exempt | |
336 // from needing to do so. | |
337 if (notification_shown || notification_needed) { | |
338 content::ServiceWorkerContext* service_worker_context = | |
339 content::BrowserContext::GetStoragePartitionForSite( | |
340 profile_, requesting_origin)->GetServiceWorkerContext(); | |
341 | |
342 PushMessagingService::GetNotificationsShownByLastFewPushes( | |
343 service_worker_context, service_worker_registration_id, | |
344 base::Bind(&PushMessagingServiceImpl::DidGetNotificationsShown, | |
345 weak_factory_.GetWeakPtr(), | |
346 requesting_origin, service_worker_registration_id, | |
347 notification_shown, notification_needed)); | |
348 } else { | |
349 RecordUserVisibleStatus( | |
350 content::PUSH_USER_VISIBLE_STATUS_NOT_REQUIRED_AND_NOT_SHOWN); | |
351 } | |
352 #endif // defined(ENABLE_NOTIFICATIONS) | |
353 } | |
354 | |
355 static void IgnoreResult(bool unused) { | |
356 } | |
357 | |
358 void PushMessagingServiceImpl::DidGetNotificationsShown( | |
359 const GURL& requesting_origin, int64 service_worker_registration_id, | |
360 bool notification_shown, bool notification_needed, | |
361 const std::string& data, bool success, bool not_found) { | |
362 content::ServiceWorkerContext* service_worker_context = | |
363 content::BrowserContext::GetStoragePartitionForSite( | |
364 profile_, requesting_origin)->GetServiceWorkerContext(); | |
365 | |
366 // We remember whether the last (up to) 10 pushes showed notifications. | |
367 const size_t MISSED_NOTIFICATIONS_LENGTH = 10; | |
368 // data is a string like "0001000", where '0' means shown, and '1' means | |
369 // needed but not shown. We manipulate it in bitset form. | |
370 std::bitset<MISSED_NOTIFICATIONS_LENGTH> missed_notifications(data); | |
371 | |
372 bool needed_but_not_shown = notification_needed && !notification_shown; | |
373 | |
374 // New entries go at the end, and old ones are shifted off the beginning once | |
375 // the history length is exceeded. | |
376 missed_notifications <<= 1; | |
377 missed_notifications[0] = needed_but_not_shown; | |
378 std::string updated_data(missed_notifications. | |
379 to_string<char, std::string::traits_type, std::string::allocator_type>()); | |
380 PushMessagingService::SetNotificationsShownByLastFewPushes( | |
381 service_worker_context, service_worker_registration_id, | |
382 requesting_origin, updated_data, | |
383 base::Bind(&IgnoreResult)); // This is a heuristic; ignore failure. | |
384 | |
385 if (notification_shown) { | |
386 RecordUserVisibleStatus( | |
387 notification_needed | |
388 ? content::PUSH_USER_VISIBLE_STATUS_REQUIRED_AND_SHOWN | |
389 : content::PUSH_USER_VISIBLE_STATUS_NOT_REQUIRED_BUT_SHOWN); | |
390 return; | |
391 } | |
392 if (needed_but_not_shown) { | |
393 if (missed_notifications.count() <= 1) { | |
394 RecordUserVisibleStatus( | |
395 content::PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_USED_GRACE); | |
396 return; | |
397 } | |
398 RecordUserVisibleStatus( | |
399 content:: | |
400 PUSH_USER_VISIBLE_STATUS_REQUIRED_BUT_NOT_SHOWN_GRACE_EXCEEDED); | |
401 // The site failed to show a notification when one was needed, and they have | |
402 // already failed once in the previous 10 push messages, so we will show a | |
403 // generic notification. See https://crbug.com/437277. | |
404 // TODO(johnme): The generic notification should probably automatically | |
405 // close itself when the next push message arrives? | |
406 content::PlatformNotificationData notification_data; | |
407 // TODO(johnme): Switch to FormatOriginForDisplay from crbug.com/402698 | |
408 notification_data.title = base::UTF8ToUTF16(requesting_origin.host()); | |
409 notification_data.direction = | |
410 content::PlatformNotificationData::NotificationDirectionLeftToRight; | |
411 notification_data.body = | |
412 l10n_util::GetStringUTF16(IDS_PUSH_MESSAGING_GENERIC_NOTIFICATION_BODY); | |
413 notification_data.tag = | |
414 base::ASCIIToUTF16(kPushMessagingForcedNotificationTag); | |
415 notification_data.icon = GURL(); // TODO(johnme): Better icon? | |
416 PlatformNotificationServiceImpl* notification_service = | |
417 PlatformNotificationServiceImpl::GetInstance(); | |
418 notification_service->DisplayPersistentNotification( | |
419 profile_, | |
420 service_worker_registration_id, | |
421 requesting_origin, | |
422 SkBitmap() /* icon */, | |
423 notification_data); | |
424 } | |
425 } | |
426 | |
427 // Other GCMAppHandler methods ------------------------------------------------- | |
428 | |
429 void PushMessagingServiceImpl::OnMessagesDeleted(const std::string& app_id) { | |
430 // TODO(mvanouwerkerk): Fire push error event on the Service Worker | |
431 // corresponding to app_id. | |
432 } | |
433 | |
434 void PushMessagingServiceImpl::OnSendError( | |
435 const std::string& app_id, | |
436 const GCMClient::SendErrorDetails& send_error_details) { | |
437 NOTREACHED() << "The Push API shouldn't have sent messages upstream"; | |
438 } | |
439 | |
440 void PushMessagingServiceImpl::OnSendAcknowledged( | |
441 const std::string& app_id, | |
442 const std::string& message_id) { | |
443 NOTREACHED() << "The Push API shouldn't have sent messages upstream"; | |
444 } | |
445 | |
446 // GetPushEndpoint method ------------------------------------------------------ | |
447 | |
448 GURL PushMessagingServiceImpl::GetPushEndpoint() { | |
449 return GURL(std::string(kPushMessagingEndpoint)); | |
450 } | |
451 | |
452 // Register and GetPermissionStatus methods ------------------------------------ | |
453 | |
454 void PushMessagingServiceImpl::RegisterFromDocument( | |
455 const GURL& requesting_origin, | |
456 int64 service_worker_registration_id, | |
457 const std::string& sender_id, | |
458 int renderer_id, | |
459 int render_frame_id, | |
460 bool user_visible_only, | |
461 const content::PushMessagingService::RegisterCallback& callback) { | |
462 if (!gcm_profile_service_->driver()) { | |
463 NOTREACHED() << "There is no GCMDriver. Has GCMProfileService shut down?"; | |
464 return; | |
465 } | |
466 | |
467 PushMessagingApplicationId application_id = | |
468 PushMessagingApplicationId::Generate(requesting_origin, | |
469 service_worker_registration_id); | |
470 DCHECK(application_id.IsValid()); | |
471 | |
472 if (push_registration_count_ + pending_push_registration_count_ | |
473 >= kMaxRegistrations) { | |
474 RegisterEnd(callback, | |
475 std::string(), | |
476 content::PUSH_REGISTRATION_STATUS_LIMIT_REACHED); | |
477 return; | |
478 } | |
479 | |
480 content::RenderFrameHost* render_frame_host = | |
481 content::RenderFrameHost::FromID(renderer_id, render_frame_id); | |
482 if (!render_frame_host) | |
483 return; | |
484 | |
485 content::WebContents* web_contents = | |
486 content::WebContents::FromRenderFrameHost(render_frame_host); | |
487 if (!web_contents) | |
488 return; | |
489 | |
490 // TODO(miguelg) need to send this over IPC when bubble support is | |
491 // implemented. | |
492 int bridge_id = -1; | |
493 | |
494 const PermissionRequestID id( | |
495 renderer_id, web_contents->GetRoutingID(), bridge_id, GURL()); | |
496 | |
497 gcm::PushMessagingPermissionContext* permission_context = | |
498 gcm::PushMessagingPermissionContextFactory::GetForProfile(profile_); | |
499 | |
500 if (permission_context == NULL || !user_visible_only) { | |
501 RegisterEnd(callback, | |
502 std::string(), | |
503 content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED); | |
504 return; | |
505 } | |
506 | |
507 // TODO(miguelg): Consider the value of |user_visible_only| when making | |
508 // the permission request. | |
509 // TODO(mlamouri): Move requesting Push permission over to using Mojo, and | |
510 // re-introduce the ability of |user_gesture| when bubbles require this. | |
511 // https://crbug.com/423770. | |
512 permission_context->RequestPermission( | |
513 web_contents, id, requesting_origin, true /* user_gesture */, | |
514 base::Bind(&PushMessagingServiceImpl::DidRequestPermission, | |
515 weak_factory_.GetWeakPtr(), application_id, sender_id, | |
516 callback)); | |
517 } | |
518 | |
519 void PushMessagingServiceImpl::RegisterFromWorker( | |
520 const GURL& requesting_origin, | |
521 int64 service_worker_registration_id, | |
522 const std::string& sender_id, | |
523 const content::PushMessagingService::RegisterCallback& register_callback) { | |
524 if (!gcm_profile_service_->driver()) { | |
525 NOTREACHED() << "There is no GCMDriver. Has GCMProfileService shut down?"; | |
526 return; | |
527 } | |
528 | |
529 PushMessagingApplicationId application_id = | |
530 PushMessagingApplicationId::Generate(requesting_origin, | |
531 service_worker_registration_id); | |
532 DCHECK(application_id.IsValid()); | |
533 | |
534 if (profile_->GetPrefs()->GetInteger( | |
535 prefs::kPushMessagingRegistrationCount) >= kMaxRegistrations) { | |
536 RegisterEnd(register_callback, std::string(), | |
537 content::PUSH_REGISTRATION_STATUS_LIMIT_REACHED); | |
538 return; | |
539 } | |
540 | |
541 GURL embedding_origin = requesting_origin; | |
542 blink::WebPushPermissionStatus permission_status = | |
543 PushMessagingServiceImpl::GetPermissionStatus(requesting_origin, | |
544 embedding_origin); | |
545 if (permission_status != blink::WebPushPermissionStatusGranted) { | |
546 RegisterEnd(register_callback, std::string(), | |
547 content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED); | |
548 return; | |
549 } | |
550 | |
551 IncreasePushRegistrationCount(1, true /* is_pending */); | |
552 std::vector<std::string> sender_ids(1, sender_id); | |
553 gcm_profile_service_->driver()->Register( | |
554 application_id.app_id_guid(), sender_ids, | |
555 base::Bind(&PushMessagingServiceImpl::DidRegister, | |
556 weak_factory_.GetWeakPtr(), | |
557 application_id, register_callback)); | |
558 } | |
559 | |
560 blink::WebPushPermissionStatus PushMessagingServiceImpl::GetPermissionStatus( | |
561 const GURL& requesting_origin, | |
562 const GURL& embedding_origin) { | |
563 PushMessagingPermissionContext* permission_context = | |
564 PushMessagingPermissionContextFactory::GetForProfile(profile_); | |
565 return ToPushPermission(permission_context->GetPermissionStatus( | |
566 requesting_origin, embedding_origin)); | |
567 } | |
568 | |
569 void PushMessagingServiceImpl::RegisterEnd( | |
570 const content::PushMessagingService::RegisterCallback& callback, | |
571 const std::string& registration_id, | |
572 content::PushRegistrationStatus status) { | |
573 callback.Run(registration_id, status); | |
574 } | |
575 | |
576 void PushMessagingServiceImpl::DidRegister( | |
577 const PushMessagingApplicationId& application_id, | |
578 const content::PushMessagingService::RegisterCallback& callback, | |
579 const std::string& registration_id, | |
580 GCMClient::Result result) { | |
581 content::PushRegistrationStatus status = | |
582 content::PUSH_REGISTRATION_STATUS_SERVICE_ERROR; | |
583 switch (result) { | |
584 case GCMClient::SUCCESS: | |
585 status = content::PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE; | |
586 application_id.PersistToDisk(profile_); | |
587 IncreasePushRegistrationCount(1, false /* is_pending */); | |
588 break; | |
589 case GCMClient::INVALID_PARAMETER: | |
590 case GCMClient::GCM_DISABLED: | |
591 case GCMClient::ASYNC_OPERATION_PENDING: | |
592 case GCMClient::SERVER_ERROR: | |
593 case GCMClient::UNKNOWN_ERROR: | |
594 status = content::PUSH_REGISTRATION_STATUS_SERVICE_ERROR; | |
595 break; | |
596 case GCMClient::NETWORK_ERROR: | |
597 case GCMClient::TTL_EXCEEDED: | |
598 status = content::PUSH_REGISTRATION_STATUS_NETWORK_ERROR; | |
599 break; | |
600 } | |
601 RegisterEnd(callback, registration_id, status); | |
602 DecreasePushRegistrationCount(1, true /* was_pending */); | |
603 } | |
604 | |
605 void PushMessagingServiceImpl::DidRequestPermission( | |
606 const PushMessagingApplicationId& application_id, | |
607 const std::string& sender_id, | |
608 const content::PushMessagingService::RegisterCallback& register_callback, | |
609 ContentSetting content_setting) { | |
610 if (content_setting != CONTENT_SETTING_ALLOW) { | |
611 RegisterEnd(register_callback, | |
612 std::string(), | |
613 content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED); | |
614 return; | |
615 } | |
616 | |
617 // The GCMDriver could be NULL if GCMProfileService has been shut down. | |
618 if (!gcm_profile_service_->driver()) | |
619 return; | |
620 | |
621 IncreasePushRegistrationCount(1, true /* is_pending */); | |
622 std::vector<std::string> sender_ids(1, sender_id); | |
623 gcm_profile_service_->driver()->Register( | |
624 application_id.app_id_guid(), | |
625 sender_ids, | |
626 base::Bind(&PushMessagingServiceImpl::DidRegister, | |
627 weak_factory_.GetWeakPtr(), | |
628 application_id, register_callback)); | |
629 } | |
630 | |
631 // Unregister methods ---------------------------------------------------------- | |
632 | |
633 void PushMessagingServiceImpl::Unregister( | |
634 const GURL& requesting_origin, | |
635 int64 service_worker_registration_id, | |
636 const std::string& sender_id, | |
637 bool retry_on_failure, | |
638 const content::PushMessagingService::UnregisterCallback& callback) { | |
639 DCHECK(gcm_profile_service_->driver()); | |
640 | |
641 PushMessagingApplicationId application_id = PushMessagingApplicationId::Get( | |
642 profile_, requesting_origin, service_worker_registration_id); | |
643 if (!application_id.IsValid()) { | |
644 if (!callback.is_null()) { | |
645 callback.Run( | |
646 content::PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED); | |
647 } | |
648 return; | |
649 } | |
650 | |
651 Unregister(application_id.app_id_guid(), sender_id, retry_on_failure, | |
652 callback); | |
653 } | |
654 | |
655 void PushMessagingServiceImpl::Unregister( | |
656 const std::string& app_id_guid, | |
657 const std::string& sender_id, | |
658 bool retry_on_failure, | |
659 const content::PushMessagingService::UnregisterCallback& callback) { | |
660 DCHECK(gcm_profile_service_->driver()); | |
661 | |
662 if (retry_on_failure) { | |
663 // Delete the mapping for this app id, to guarantee that no messages get | |
664 // delivered in future (even if unregistration fails). | |
665 // TODO(johnme): Instead of deleting these app ids, store them elsewhere, | |
666 // and retry unregistration if it fails due to network errors. | |
667 PushMessagingApplicationId application_id = | |
668 PushMessagingApplicationId::Get(profile_, app_id_guid); | |
669 if (application_id.IsValid()) | |
670 application_id.DeleteFromDisk(profile_); | |
671 } | |
672 | |
673 const auto& unregister_callback = | |
674 base::Bind(&PushMessagingServiceImpl::DidUnregister, | |
675 weak_factory_.GetWeakPtr(), | |
676 app_id_guid, retry_on_failure, callback); | |
677 #if defined(OS_ANDROID) | |
678 // On Android the backend is different, and requires the original sender_id. | |
679 gcm_profile_service_->driver()->UnregisterWithSenderId(app_id_guid, sender_id, | |
680 unregister_callback); | |
681 #else | |
682 gcm_profile_service_->driver()->Unregister(app_id_guid, unregister_callback); | |
683 #endif | |
684 } | |
685 | |
686 void PushMessagingServiceImpl::DidUnregister( | |
687 const std::string& app_id_guid, | |
688 bool retry_on_failure, | |
689 const content::PushMessagingService::UnregisterCallback& callback, | |
690 GCMClient::Result result) { | |
691 if (result == GCMClient::SUCCESS) { | |
692 PushMessagingApplicationId application_id = | |
693 PushMessagingApplicationId::Get(profile_, app_id_guid); | |
694 if (!application_id.IsValid()) { | |
695 if (!callback.is_null()) { | |
696 callback.Run( | |
697 content::PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED); | |
698 } | |
699 return; | |
700 } | |
701 | |
702 application_id.DeleteFromDisk(profile_); | |
703 DecreasePushRegistrationCount(1, false /* was_pending */); | |
704 } | |
705 | |
706 // Internal calls pass a null callback. | |
707 if (!callback.is_null()) { | |
708 switch (result) { | |
709 case GCMClient::SUCCESS: | |
710 callback.Run(content::PUSH_UNREGISTRATION_STATUS_SUCCESS_UNREGISTERED); | |
711 break; | |
712 case GCMClient::INVALID_PARAMETER: | |
713 case GCMClient::GCM_DISABLED: | |
714 case GCMClient::ASYNC_OPERATION_PENDING: | |
715 case GCMClient::SERVER_ERROR: | |
716 case GCMClient::UNKNOWN_ERROR: | |
717 callback.Run(content::PUSH_UNREGISTRATION_STATUS_SERVICE_ERROR); | |
718 break; | |
719 case GCMClient::NETWORK_ERROR: | |
720 case GCMClient::TTL_EXCEEDED: | |
721 callback.Run( | |
722 retry_on_failure | |
723 ? content:: | |
724 PUSH_UNREGISTRATION_STATUS_PENDING_WILL_RETRY_NETWORK_ERROR | |
725 : content::PUSH_UNREGISTRATION_STATUS_NETWORK_ERROR); | |
726 break; | |
727 } | |
728 } | |
729 } | |
730 | |
731 // OnContentSettingChanged methods --------------------------------------------- | |
732 | |
733 void PushMessagingServiceImpl::OnContentSettingChanged( | |
734 const ContentSettingsPattern& primary_pattern, | |
735 const ContentSettingsPattern& secondary_pattern, | |
736 ContentSettingsType content_type, | |
737 std::string resource_identifier) { | |
738 if (content_type != CONTENT_SETTINGS_TYPE_PUSH_MESSAGING && | |
739 content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS) { | |
740 return; | |
741 } | |
742 | |
743 for (const auto& id : PushMessagingApplicationId::GetAll(profile_)) { | |
744 // If |primary_pattern| is not valid, we should always check for a | |
745 // permission change because it can happen for example when the entire | |
746 // Push or Notifications permissions are cleared. | |
747 // Otherwise, the permission should be checked if the pattern matches the | |
748 // origin. | |
749 if (primary_pattern.IsValid() && !primary_pattern.Matches(id.origin())) | |
750 continue; | |
751 | |
752 if (HasPermission(id.origin())) | |
753 continue; | |
754 | |
755 PushMessagingService::GetSenderId( | |
756 profile_, id.origin(), id.service_worker_registration_id(), | |
757 base::Bind( | |
758 &PushMessagingServiceImpl::UnregisterBecausePermissionRevoked, | |
759 weak_factory_.GetWeakPtr(), id)); | |
760 } | |
761 } | |
762 | |
763 void PushMessagingServiceImpl::UnregisterBecausePermissionRevoked( | |
764 const PushMessagingApplicationId& id, | |
765 const std::string& sender_id, bool success, bool not_found) { | |
766 // Unregister the PushMessagingApplicationId with the push service. | |
767 Unregister(id.app_id_guid(), sender_id, true /* retry_on_failure */, | |
768 UnregisterCallback()); | |
769 | |
770 // Clear the associated service worker push registration id. | |
771 PushMessagingService::ClearPushRegistrationID( | |
772 profile_, id.origin(), id.service_worker_registration_id()); | |
773 } | |
774 | |
775 // Helper methods -------------------------------------------------------------- | |
776 | |
777 bool PushMessagingServiceImpl::HasPermission(const GURL& origin) { | |
778 gcm::PushMessagingPermissionContext* permission_context = | |
779 gcm::PushMessagingPermissionContextFactory::GetForProfile(profile_); | |
780 DCHECK(permission_context); | |
781 | |
782 return permission_context->GetPermissionStatus(origin, origin) == | |
783 CONTENT_SETTING_ALLOW; | |
784 } | |
785 | |
786 void PushMessagingServiceImpl::SetProfileForTesting(Profile* profile) { | |
787 profile_ = profile; | |
788 profile_->GetHostContentSettingsMap()->AddObserver(this); | |
789 } | |
790 | |
791 } // namespace gcm | |
OLD | NEW |