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.
5 #include "chrome/browser/services/gcm/push_messaging_service_impl.h"
10 #include "base/command_line.h"
11 #include "base/prefs/pref_service.h"
12 #include "base/strings/string_util.h"
13 #include "chrome/browser/profiles/profile.h"
14 #include "chrome/browser/services/gcm/gcm_profile_service.h"
15 #include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
16 #include "chrome/browser/services/gcm/push_messaging_application_id.h"
17 #include "chrome/browser/services/gcm/push_messaging_constants.h"
18 #include "chrome/browser/services/gcm/push_messaging_permission_context.h"
19 #include "chrome/browser/services/gcm/push_messaging_permission_context_factory.h"
20 #include "chrome/common/chrome_switches.h"
21 #include "chrome/common/pref_names.h"
22 #include "components/content_settings/core/common/permission_request_id.h"
23 #include "components/gcm_driver/gcm_driver.h"
24 #include "components/pref_registry/pref_registry_syncable.h"
25 #include "content/public/browser/browser_context.h"
26 #include "content/public/browser/render_frame_host.h"
27 #include "content/public/browser/web_contents.h"
32 const int kMaxRegistrations = 1000000;
34 blink::WebPushPermissionStatus ToPushPermission(ContentSetting setting) {
36 case CONTENT_SETTING_ALLOW:
37 return blink::WebPushPermissionStatusGranted;
38 case CONTENT_SETTING_BLOCK:
39 return blink::WebPushPermissionStatusDenied;
40 case CONTENT_SETTING_ASK:
41 return blink::WebPushPermissionStatusDefault;
44 return blink::WebPushPermissionStatusDenied;
51 void PushMessagingServiceImpl::RegisterProfilePrefs(
52 user_prefs::PrefRegistrySyncable* registry) {
53 registry->RegisterIntegerPref(
54 prefs::kPushMessagingRegistrationCount,
56 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
60 void PushMessagingServiceImpl::InitializeForProfile(Profile* profile) {
61 // TODO(mvanouwerkerk): Make sure to remove this check at the same time as
62 // push graduates from experimental in Blink.
63 if (!CommandLine::ForCurrentProcess()->HasSwitch(
64 switches::kEnableExperimentalWebPlatformFeatures)) {
67 // TODO(johnme): Consider whether push should be enabled in incognito. If it
68 // does get enabled, then be careful that you're reading the pref from the
69 // right profile, as prefs defined in a regular profile are visible in the
70 // corresponding incognito profile unless overrriden.
71 if (!profile || profile->IsOffTheRecord() ||
72 profile->GetPrefs()->GetInteger(prefs::kPushMessagingRegistrationCount) <=
76 // Create the GCMProfileService, and hence instantiate this class.
77 GCMProfileService* gcm_service =
78 gcm::GCMProfileServiceFactory::GetForProfile(profile);
79 PushMessagingServiceImpl* push_service =
80 static_cast<PushMessagingServiceImpl*>(
81 gcm_service->push_messaging_service());
82 // Register ourselves as an app handler.
83 gcm_service->driver()->AddAppHandler(kPushMessagingApplicationIdPrefix,
87 PushMessagingServiceImpl::PushMessagingServiceImpl(
88 GCMProfileService* gcm_profile_service,
90 : gcm_profile_service_(gcm_profile_service),
95 PushMessagingServiceImpl::~PushMessagingServiceImpl() {
96 // TODO(johnme): If it's possible for this to be destroyed before GCMDriver,
97 // then we should call RemoveAppHandler.
100 bool PushMessagingServiceImpl::CanHandle(const std::string& app_id) const {
101 return PushMessagingApplicationId::Parse(app_id).IsValid();
104 void PushMessagingServiceImpl::ShutdownHandler() {
105 // TODO(johnme): Do any necessary cleanup.
108 void PushMessagingServiceImpl::OnMessage(
109 const std::string& app_id,
110 const GCMClient::IncomingMessage& message) {
111 // The Push API only exposes a single string of data in the push event fired
112 // on the Service Worker. When developers send messages using GCM to the Push
113 // API, they must pass a single key-value pair, where the key is "data" and
114 // the value is the string they want to be passed to their Service Worker.
115 // For example, they could send the following JSON using the HTTPS GCM API:
117 // "registration_ids": ["FOO", "BAR"],
121 // "delay_while_idle": true,
123 // TODO(johnme): Make sure this is clearly documented for developers.
124 PushMessagingApplicationId application_id =
125 PushMessagingApplicationId::Parse(app_id);
126 DCHECK(application_id.IsValid());
127 GCMClient::MessageData::const_iterator it = message.data.find("data");
128 if (application_id.IsValid() && it != message.data.end()) {
129 const std::string& data = it->second;
130 content::BrowserContext::DeliverPushMessage(
132 application_id.origin,
133 application_id.service_worker_registration_id,
135 base::Bind(&PushMessagingServiceImpl::DeliverMessageCallback,
136 weak_factory_.GetWeakPtr(),
140 // Drop the message, as it is invalid.
141 // TODO(mvanouwerkerk): Show a warning in the developer console of the
142 // Service Worker corresponding to app_id.
143 // TODO(johnme): Add diagnostic observers (e.g. UMA and an internals page)
144 // to know when bad things happen.
148 void PushMessagingServiceImpl::SetProfileForTesting(Profile* profile) {
152 void PushMessagingServiceImpl::DeliverMessageCallback(
153 const PushMessagingApplicationId& application_id,
154 const GCMClient::IncomingMessage& message,
155 content::PushDeliveryStatus status) {
156 // TODO(mvanouwerkerk): UMA logging.
157 // TODO(mvanouwerkerk): Is there a way to recover from failure?
160 void PushMessagingServiceImpl::OnMessagesDeleted(const std::string& app_id) {
161 // TODO(mvanouwerkerk): Fire push error event on the Service Worker
162 // corresponding to app_id.
165 void PushMessagingServiceImpl::OnSendError(
166 const std::string& app_id,
167 const GCMClient::SendErrorDetails& send_error_details) {
168 NOTREACHED() << "The Push API shouldn't have sent messages upstream";
171 void PushMessagingServiceImpl::OnSendAcknowledged(
172 const std::string& app_id,
173 const std::string& message_id) {
174 NOTREACHED() << "The Push API shouldn't have sent messages upstream";
177 void PushMessagingServiceImpl::Register(
179 int64 service_worker_registration_id,
180 const std::string& sender_id,
184 const content::PushMessagingService::RegisterCallback& callback) {
185 if (!gcm_profile_service_->driver()) {
186 NOTREACHED() << "There is no GCMDriver. Has GCMProfileService shut down?";
189 PushMessagingApplicationId application_id =
190 PushMessagingApplicationId(origin, service_worker_registration_id);
191 DCHECK(application_id.IsValid());
193 if (profile_->GetPrefs()->GetInteger(
194 prefs::kPushMessagingRegistrationCount) >= kMaxRegistrations) {
195 RegisterEnd(callback,
197 content::PUSH_REGISTRATION_STATUS_LIMIT_REACHED);
201 // If this is registering for the first time then the driver does not have
202 // this as an app handler and registration would fail.
203 if (gcm_profile_service_->driver()->GetAppHandler(
204 kPushMessagingApplicationIdPrefix) != this)
205 gcm_profile_service_->driver()->AddAppHandler(
206 kPushMessagingApplicationIdPrefix, this);
208 content::RenderFrameHost* render_frame_host =
209 content::RenderFrameHost::FromID(renderer_id, render_frame_id);
211 // The frame doesn't exist any more, or we received a bad frame id.
212 if (!render_frame_host)
215 content::WebContents* web_contents =
216 content::WebContents::FromRenderFrameHost(render_frame_host);
218 // The page doesn't exist any more or we got a bad render frame host.
222 // TODO(miguelg) need to send this over IPC when bubble support is
226 const PermissionRequestID id(
227 renderer_id, web_contents->GetRoutingID(), bridge_id, GURL());
229 GURL embedder = web_contents->GetLastCommittedURL();
230 gcm::PushMessagingPermissionContext* permission_context =
231 gcm::PushMessagingPermissionContextFactory::GetForProfile(profile_);
233 if (permission_context == NULL) {
234 RegisterEnd(callback,
236 content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
240 permission_context->RequestPermission(
245 base::Bind(&PushMessagingServiceImpl::DidRequestPermission,
246 weak_factory_.GetWeakPtr(),
252 blink::WebPushPermissionStatus PushMessagingServiceImpl::GetPermissionStatus(
253 const GURL& requesting_origin,
255 int render_frame_id) {
256 content::RenderFrameHost* render_frame_host =
257 content::RenderFrameHost::FromID(renderer_id, render_frame_id);
259 // The frame doesn't exist any more, or we received a bad frame id.
260 if (!render_frame_host)
261 return blink::WebPushPermissionStatusDenied;
263 content::WebContents* web_contents =
264 content::WebContents::FromRenderFrameHost(render_frame_host);
266 // The page doesn't exist any more or we got a bad render frame host.
268 return blink::WebPushPermissionStatusDenied;
270 GURL embedder_origin = web_contents->GetLastCommittedURL().GetOrigin();
271 gcm::PushMessagingPermissionContext* permission_context =
272 gcm::PushMessagingPermissionContextFactory::GetForProfile(profile_);
273 return ToPushPermission(
274 permission_context->GetPermissionStatus(
275 requesting_origin, embedder_origin));
278 void PushMessagingServiceImpl::RegisterEnd(
279 const content::PushMessagingService::RegisterCallback& callback,
280 const std::string& registration_id,
281 content::PushRegistrationStatus status) {
282 GURL endpoint = GURL(std::string(kPushMessagingEndpoint));
283 callback.Run(endpoint, registration_id, status);
284 if (status == content::PUSH_REGISTRATION_STATUS_SUCCESS) {
285 // TODO(johnme): Make sure the pref doesn't get out of sync after crashes.
286 int registration_count = profile_->GetPrefs()->GetInteger(
287 prefs::kPushMessagingRegistrationCount);
288 profile_->GetPrefs()->SetInteger(prefs::kPushMessagingRegistrationCount,
289 registration_count + 1);
293 void PushMessagingServiceImpl::DidRegister(
294 const content::PushMessagingService::RegisterCallback& callback,
295 const std::string& registration_id,
296 GCMClient::Result result) {
297 content::PushRegistrationStatus status =
298 result == GCMClient::SUCCESS
299 ? content::PUSH_REGISTRATION_STATUS_SUCCESS
300 : content::PUSH_REGISTRATION_STATUS_SERVICE_ERROR;
301 RegisterEnd(callback, registration_id, status);
304 void PushMessagingServiceImpl::DidRequestPermission(
305 const PushMessagingApplicationId& application_id,
306 const std::string& sender_id,
307 const content::PushMessagingService::RegisterCallback& register_callback,
310 RegisterEnd(register_callback,
312 content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
316 // The GCMDriver could be NULL if GCMProfileService has been shut down.
317 if (!gcm_profile_service_->driver())
320 std::vector<std::string> sender_ids(1, sender_id);
322 gcm_profile_service_->driver()->Register(
323 application_id.ToString(),
325 base::Bind(&PushMessagingServiceImpl::DidRegister,
326 weak_factory_.GetWeakPtr(),
330 // TODO(johnme): Unregister should decrement the pref, and call
331 // RemoveAppHandler if the count drops to zero.