blob: 7d5f26fbeb49227db112ba5eb483ef3bd31ad36b [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/push_messaging/push_messaging_service_impl.h"
#include <vector>
#include "base/barrier_closure.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback_helpers.h"
#include "base/command_line.h"
#include "base/logging.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "build/build_config.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
#include "chrome/browser/permissions/permission_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/push_messaging/push_messaging_app_identifier.h"
#include "chrome/browser/push_messaging/push_messaging_constants.h"
#include "chrome/browser/push_messaging/push_messaging_service_factory.h"
#include "chrome/browser/push_messaging/push_messaging_service_observer.h"
#include "chrome/browser/services/gcm/gcm_profile_service_factory.h"
#include "chrome/browser/ui/chrome_pages.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "chrome/grit/generated_resources.h"
#include "components/content_settings/core/browser/host_content_settings_map.h"
#include "components/gcm_driver/gcm_driver.h"
#include "components/gcm_driver/gcm_profile_service.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/rappor/rappor_utils.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/permission_type.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"
#include "content/public/common/push_messaging_status.h"
#include "ui/base/l10n/l10n_util.h"
#if defined(ENABLE_BACKGROUND)
#include "chrome/browser/background/background_mode_manager.h"
#endif
namespace {
const int kMaxRegistrations = 1000000;
// Chrome does not yet support silent push messages, and requires websites to
// indicate that they will only send user-visible messages.
const char kSilentPushUnsupportedMessage[] =
"Chrome currently only supports the Push API for subscriptions that will "
"result in user-visible messages. You can indicate this by calling "
"pushManager.subscribe({userVisibleOnly: true}) instead. See "
"https://goo.gl/yqv4Q4 for more details.";
void RecordDeliveryStatus(content::PushDeliveryStatus status) {
UMA_HISTOGRAM_ENUMERATION("PushMessaging.DeliveryStatus", status,
content::PUSH_DELIVERY_STATUS_LAST + 1);
}
blink::WebPushPermissionStatus ToPushPermission(
content::PermissionStatus permission_status) {
switch (permission_status) {
case content::PermissionStatus::GRANTED:
return blink::WebPushPermissionStatusGranted;
case content::PermissionStatus::DENIED:
return blink::WebPushPermissionStatusDenied;
case content::PermissionStatus::ASK:
return blink::WebPushPermissionStatusPrompt;
default:
NOTREACHED();
return blink::WebPushPermissionStatusDenied;
}
}
void UnregisterCallbackToClosure(const base::Closure& closure,
content::PushUnregistrationStatus status) {
closure.Run();
}
#if defined(ENABLE_BACKGROUND)
bool UseBackgroundMode() {
// Note: if push is ever enabled in incognito, the background mode integration
// should not be enabled for it.
std::string group_name =
base::FieldTrialList::FindFullName("PushApiBackgroundMode");
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kDisablePushApiBackgroundMode))
return false;
if (command_line->HasSwitch(switches::kEnablePushApiBackgroundMode))
return true;
return group_name == "Enabled";
}
#endif // defined(ENABLE_BACKGROUND)
} // namespace
// static
void PushMessagingServiceImpl::InitializeForProfile(Profile* profile) {
// TODO(johnme): Consider whether push should be enabled in incognito.
if (!profile || profile->IsOffTheRecord())
return;
int count = PushMessagingAppIdentifier::GetCount(profile);
if (count <= 0)
return;
PushMessagingServiceImpl* push_service =
PushMessagingServiceFactory::GetForProfile(profile);
push_service->IncreasePushSubscriptionCount(count, false /* is_pending */);
}
PushMessagingServiceImpl::PushMessagingServiceImpl(Profile* profile)
: profile_(profile),
push_subscription_count_(0),
pending_push_subscription_count_(0),
#if defined(ENABLE_NOTIFICATIONS)
notification_manager_(profile),
#endif
push_messaging_service_observer_(PushMessagingServiceObserver::Create()),
weak_factory_(this) {
DCHECK(profile);
HostContentSettingsMapFactory::GetForProfile(profile_)->AddObserver(this);
}
PushMessagingServiceImpl::~PushMessagingServiceImpl() {
HostContentSettingsMapFactory::GetForProfile(profile_)->RemoveObserver(this);
}
void PushMessagingServiceImpl::IncreasePushSubscriptionCount(int add,
bool is_pending) {
DCHECK(add > 0);
if (push_subscription_count_ + pending_push_subscription_count_ == 0) {
GetGCMDriver()->AddAppHandler(kPushMessagingAppIdentifierPrefix, this);
}
if (is_pending) {
pending_push_subscription_count_ += add;
} else {
#if defined(ENABLE_BACKGROUND)
if (UseBackgroundMode() && g_browser_process->background_mode_manager() &&
!push_subscription_count_) {
g_browser_process->background_mode_manager()->RegisterTrigger(
profile_, this, false /* should_notify_user */);
}
#endif // defined(ENABLE_BACKGROUND)
push_subscription_count_ += add;
}
}
void PushMessagingServiceImpl::DecreasePushSubscriptionCount(int subtract,
bool was_pending) {
DCHECK(subtract > 0);
if (was_pending) {
pending_push_subscription_count_ -= subtract;
DCHECK(pending_push_subscription_count_ >= 0);
} else {
push_subscription_count_ -= subtract;
DCHECK(push_subscription_count_ >= 0);
}
if (push_subscription_count_ + pending_push_subscription_count_ == 0) {
GetGCMDriver()->RemoveAppHandler(kPushMessagingAppIdentifierPrefix);
#if defined(ENABLE_BACKGROUND)
if (UseBackgroundMode() && g_browser_process->background_mode_manager()) {
g_browser_process->background_mode_manager()->UnregisterTrigger(profile_,
this);
}
#endif // defined(ENABLE_BACKGROUND)
}
}
bool PushMessagingServiceImpl::CanHandle(const std::string& app_id) const {
return !PushMessagingAppIdentifier::FindByAppId(profile_, app_id).is_null();
}
void PushMessagingServiceImpl::ShutdownHandler() {
// Shutdown() should come before and it removes us from the list of app
// handlers of gcm::GCMDriver so this shouldn't ever been called.
NOTREACHED();
}
// OnMessage methods -----------------------------------------------------------
void PushMessagingServiceImpl::OnMessage(const std::string& app_id,
const gcm::IncomingMessage& message) {
in_flight_message_deliveries_.insert(app_id);
base::Closure message_handled_closure =
message_callback_for_testing_.is_null() ? base::Bind(&base::DoNothing)
: message_callback_for_testing_;
PushMessagingAppIdentifier app_identifier =
PushMessagingAppIdentifier::FindByAppId(profile_, app_id);
// Drop message and unregister if app_id was unknown (maybe recently deleted).
if (app_identifier.is_null()) {
DeliverMessageCallback(app_id, GURL::EmptyGURL(), -1, message,
message_handled_closure,
content::PUSH_DELIVERY_STATUS_UNKNOWN_APP_ID);
return;
}
// Drop message and unregister if |origin| has lost push permission.
if (!IsPermissionSet(app_identifier.origin())) {
DeliverMessageCallback(app_id, app_identifier.origin(),
app_identifier.service_worker_registration_id(),
message, message_handled_closure,
content::PUSH_DELIVERY_STATUS_PERMISSION_DENIED);
return;
}
rappor::SampleDomainAndRegistryFromGURL(
g_browser_process->rappor_service(),
"PushMessaging.MessageReceived.Origin", app_identifier.origin());
// The payload of a push message can be valid with content, valid with empty
// content, or null. Only set the payload data if it is non-null.
content::PushEventPayload payload;
if (AreMessagePayloadsEnabled() && message.decrypted)
payload.setData(message.raw_data);
// Dispatch the message to the appropriate Service Worker.
content::BrowserContext::DeliverPushMessage(
profile_, app_identifier.origin(),
app_identifier.service_worker_registration_id(), payload,
base::Bind(&PushMessagingServiceImpl::DeliverMessageCallback,
weak_factory_.GetWeakPtr(), app_identifier.app_id(),
app_identifier.origin(),
app_identifier.service_worker_registration_id(), message,
message_handled_closure));
// Inform tests observing message dispatching about the event.
if (!message_dispatched_callback_for_testing_.is_null()) {
message_dispatched_callback_for_testing_.Run(
app_id, app_identifier.origin(),
app_identifier.service_worker_registration_id(), payload);
}
}
void PushMessagingServiceImpl::DeliverMessageCallback(
const std::string& app_id,
const GURL& requesting_origin,
int64_t service_worker_registration_id,
const gcm::IncomingMessage& message,
const base::Closure& message_handled_closure,
content::PushDeliveryStatus status) {
DCHECK_GE(in_flight_message_deliveries_.count(app_id), 1u);
base::Closure completion_closure =
base::Bind(&PushMessagingServiceImpl::DidHandleMessage,
weak_factory_.GetWeakPtr(), app_id, message_handled_closure);
// The completion_closure should run by default at the end of this function,
// unless it is explicitly passed to another function.
base::ScopedClosureRunner completion_closure_runner(completion_closure);
// TODO(mvanouwerkerk): Show a warning in the developer console of the
// Service Worker corresponding to app_id (and/or on an internals page).
// See https://crbug.com/508516 for options.
switch (status) {
// Call EnforceUserVisibleOnlyRequirements if the message was delivered to
// the Service Worker JavaScript, even if the website's event handler failed
// (to prevent sites deliberately failing in order to avoid having to show
// notifications).
case content::PUSH_DELIVERY_STATUS_SUCCESS:
case content::PUSH_DELIVERY_STATUS_EVENT_WAITUNTIL_REJECTED:
#if defined(ENABLE_NOTIFICATIONS)
// Only enforce the user visible requirements if this is currently running
// as the delivery callback for the last in-flight message.
if (in_flight_message_deliveries_.count(app_id) == 1) {
notification_manager_.EnforceUserVisibleOnlyRequirements(
requesting_origin, service_worker_registration_id,
completion_closure_runner.Release());
}
#endif
break;
case content::PUSH_DELIVERY_STATUS_INVALID_MESSAGE:
case content::PUSH_DELIVERY_STATUS_SERVICE_WORKER_ERROR:
break;
case content::PUSH_DELIVERY_STATUS_UNKNOWN_APP_ID:
case content::PUSH_DELIVERY_STATUS_PERMISSION_DENIED:
case content::PUSH_DELIVERY_STATUS_NO_SERVICE_WORKER:
Unsubscribe(app_id, message.sender_id,
base::Bind(&UnregisterCallbackToClosure,
completion_closure_runner.Release()));
break;
}
RecordDeliveryStatus(status);
}
void PushMessagingServiceImpl::DidHandleMessage(
const std::string& app_id,
const base::Closure& message_handled_closure) {
auto in_flight_iterator = in_flight_message_deliveries_.find(app_id);
DCHECK(in_flight_iterator != in_flight_message_deliveries_.end());
// Remove a single in-flight delivery for |app_id|. This has to be done using
// an iterator rather than by value, as the latter removes all entries.
in_flight_message_deliveries_.erase(in_flight_iterator);
message_handled_closure.Run();
if (push_messaging_service_observer_)
push_messaging_service_observer_->OnMessageHandled();
}
void PushMessagingServiceImpl::SetMessageCallbackForTesting(
const base::Closure& callback) {
message_callback_for_testing_ = callback;
}
// Other gcm::GCMAppHandler methods --------------------------------------------
void PushMessagingServiceImpl::OnMessagesDeleted(const std::string& app_id) {
// TODO(mvanouwerkerk): Consider firing an event on the Service Worker
// corresponding to |app_id| to inform the app about deleted messages.
}
void PushMessagingServiceImpl::OnSendError(
const std::string& app_id,
const gcm::GCMClient::SendErrorDetails& send_error_details) {
NOTREACHED() << "The Push API shouldn't have sent messages upstream";
}
void PushMessagingServiceImpl::OnSendAcknowledged(
const std::string& app_id,
const std::string& message_id) {
NOTREACHED() << "The Push API shouldn't have sent messages upstream";
}
// GetPushEndpoint method ------------------------------------------------------
GURL PushMessagingServiceImpl::GetPushEndpoint() {
return GURL(std::string(kPushMessagingEndpoint));
}
// Subscribe and GetPermissionStatus methods -----------------------------------
void PushMessagingServiceImpl::SubscribeFromDocument(
const GURL& requesting_origin,
int64_t service_worker_registration_id,
const std::string& sender_id,
int renderer_id,
int render_frame_id,
bool user_visible,
const content::PushMessagingService::RegisterCallback& callback) {
PushMessagingAppIdentifier app_identifier =
PushMessagingAppIdentifier::Generate(requesting_origin,
service_worker_registration_id);
if (push_subscription_count_ + pending_push_subscription_count_ >=
kMaxRegistrations) {
SubscribeEndWithError(callback,
content::PUSH_REGISTRATION_STATUS_LIMIT_REACHED);
return;
}
content::RenderFrameHost* render_frame_host =
content::RenderFrameHost::FromID(renderer_id, render_frame_id);
content::WebContents* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
if (!web_contents)
return;
if (!user_visible) {
web_contents->GetMainFrame()->AddMessageToConsole(
content::CONSOLE_MESSAGE_LEVEL_ERROR, kSilentPushUnsupportedMessage);
SubscribeEndWithError(callback,
content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
return;
}
// Push does not allow permission requests from iframes.
profile_->GetPermissionManager()->RequestPermission(
content::PermissionType::PUSH_MESSAGING, web_contents->GetMainFrame(),
requesting_origin, true /* user_gesture */,
base::Bind(&PushMessagingServiceImpl::DidRequestPermission,
weak_factory_.GetWeakPtr(), app_identifier, sender_id,
callback));
}
void PushMessagingServiceImpl::SubscribeFromWorker(
const GURL& requesting_origin,
int64_t service_worker_registration_id,
const std::string& sender_id,
bool user_visible,
const content::PushMessagingService::RegisterCallback& register_callback) {
PushMessagingAppIdentifier app_identifier =
PushMessagingAppIdentifier::Generate(requesting_origin,
service_worker_registration_id);
if (push_subscription_count_ + pending_push_subscription_count_ >=
kMaxRegistrations) {
SubscribeEndWithError(register_callback,
content::PUSH_REGISTRATION_STATUS_LIMIT_REACHED);
return;
}
GURL embedding_origin = requesting_origin;
blink::WebPushPermissionStatus permission_status =
PushMessagingServiceImpl::GetPermissionStatus(
requesting_origin, embedding_origin, user_visible);
if (permission_status != blink::WebPushPermissionStatusGranted) {
SubscribeEndWithError(register_callback,
content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
return;
}
IncreasePushSubscriptionCount(1, true /* is_pending */);
std::vector<std::string> sender_ids(1, sender_id);
GetGCMDriver()->Register(app_identifier.app_id(), sender_ids,
base::Bind(&PushMessagingServiceImpl::DidSubscribe,
weak_factory_.GetWeakPtr(),
app_identifier, register_callback));
}
blink::WebPushPermissionStatus PushMessagingServiceImpl::GetPermissionStatus(
const GURL& requesting_origin,
const GURL& embedding_origin,
bool user_visible) {
if (!user_visible)
return blink::WebPushPermissionStatusDenied;
return ToPushPermission(profile_->GetPermissionManager()->GetPermissionStatus(
content::PermissionType::PUSH_MESSAGING, requesting_origin,
embedding_origin));
}
bool PushMessagingServiceImpl::SupportNonVisibleMessages() {
return false;
}
void PushMessagingServiceImpl::SubscribeEnd(
const content::PushMessagingService::RegisterCallback& callback,
const std::string& subscription_id,
const std::vector<uint8_t>& p256dh,
const std::vector<uint8_t>& auth,
content::PushRegistrationStatus status) {
callback.Run(subscription_id, p256dh, auth, status);
}
void PushMessagingServiceImpl::SubscribeEndWithError(
const content::PushMessagingService::RegisterCallback& callback,
content::PushRegistrationStatus status) {
SubscribeEnd(callback, std::string() /* subscription_id */,
std::vector<uint8_t>() /* p256dh */,
std::vector<uint8_t>() /* auth */, status);
}
void PushMessagingServiceImpl::DidSubscribe(
const PushMessagingAppIdentifier& app_identifier,
const content::PushMessagingService::RegisterCallback& callback,
const std::string& subscription_id,
gcm::GCMClient::Result result) {
DecreasePushSubscriptionCount(1, true /* was_pending */);
content::PushRegistrationStatus status =
content::PUSH_REGISTRATION_STATUS_SERVICE_ERROR;
switch (result) {
case gcm::GCMClient::SUCCESS:
// Do not get a certificate if message payloads have not been enabled.
if (!AreMessagePayloadsEnabled()) {
DidSubscribeWithEncryptionInfo(
app_identifier, callback, subscription_id,
std::string() /* p256dh */, std::string() /* auth_secret */);
return;
}
// Make sure that this subscription has associated encryption keys prior
// to returning it to the developer - they'll need this information in
// order to send payloads to the user.
GetGCMDriver()->GetEncryptionInfo(
app_identifier.app_id(),
base::Bind(&PushMessagingServiceImpl::DidSubscribeWithEncryptionInfo,
weak_factory_.GetWeakPtr(), app_identifier, callback,
subscription_id));
return;
case gcm::GCMClient::INVALID_PARAMETER:
case gcm::GCMClient::GCM_DISABLED:
case gcm::GCMClient::ASYNC_OPERATION_PENDING:
case gcm::GCMClient::SERVER_ERROR:
case gcm::GCMClient::UNKNOWN_ERROR:
status = content::PUSH_REGISTRATION_STATUS_SERVICE_ERROR;
break;
case gcm::GCMClient::NETWORK_ERROR:
case gcm::GCMClient::TTL_EXCEEDED:
status = content::PUSH_REGISTRATION_STATUS_NETWORK_ERROR;
break;
}
SubscribeEndWithError(callback, status);
}
void PushMessagingServiceImpl::DidSubscribeWithEncryptionInfo(
const PushMessagingAppIdentifier& app_identifier,
const content::PushMessagingService::RegisterCallback& callback,
const std::string& subscription_id,
const std::string& p256dh,
const std::string& auth_secret) {
if (!p256dh.size() && AreMessagePayloadsEnabled()) {
SubscribeEndWithError(
callback, content::PUSH_REGISTRATION_STATUS_PUBLIC_KEY_UNAVAILABLE);
return;
}
app_identifier.PersistToPrefs(profile_);
IncreasePushSubscriptionCount(1, false /* is_pending */);
SubscribeEnd(callback, subscription_id,
std::vector<uint8_t>(p256dh.begin(), p256dh.end()),
std::vector<uint8_t>(auth_secret.begin(), auth_secret.end()),
content::PUSH_REGISTRATION_STATUS_SUCCESS_FROM_PUSH_SERVICE);
}
void PushMessagingServiceImpl::DidRequestPermission(
const PushMessagingAppIdentifier& app_identifier,
const std::string& sender_id,
const content::PushMessagingService::RegisterCallback& register_callback,
content::PermissionStatus permission_status) {
if (permission_status != content::PermissionStatus::GRANTED) {
SubscribeEndWithError(register_callback,
content::PUSH_REGISTRATION_STATUS_PERMISSION_DENIED);
return;
}
IncreasePushSubscriptionCount(1, true /* is_pending */);
std::vector<std::string> sender_ids(1, sender_id);
GetGCMDriver()->Register(app_identifier.app_id(), sender_ids,
base::Bind(&PushMessagingServiceImpl::DidSubscribe,
weak_factory_.GetWeakPtr(),
app_identifier, register_callback));
}
// GetEncryptionInfo methods ---------------------------------------------------
void PushMessagingServiceImpl::GetEncryptionInfo(
const GURL& origin,
int64_t service_worker_registration_id,
const PushMessagingService::EncryptionInfoCallback& callback) {
// An empty public key will be returned if payloads are not enabled.
if (!AreMessagePayloadsEnabled()) {
callback.Run(true /* success */, std::vector<uint8_t>() /* p256dh */,
std::vector<uint8_t>() /* auth */);
return;
}
PushMessagingAppIdentifier app_identifier =
PushMessagingAppIdentifier::FindByServiceWorker(
profile_, origin, service_worker_registration_id);
DCHECK(!app_identifier.is_null());
GetGCMDriver()->GetEncryptionInfo(
app_identifier.app_id(),
base::Bind(&PushMessagingServiceImpl::DidGetEncryptionInfo,
weak_factory_.GetWeakPtr(), callback));
}
void PushMessagingServiceImpl::DidGetEncryptionInfo(
const PushMessagingService::EncryptionInfoCallback& callback,
const std::string& p256dh,
const std::string& auth_secret) const {
// I/O errors might prevent the GCM Driver from retrieving a key-pair.
const bool success = !!p256dh.size();
callback.Run(success, std::vector<uint8_t>(p256dh.begin(), p256dh.end()),
std::vector<uint8_t>(auth_secret.begin(), auth_secret.end()));
}
// Unsubscribe methods ---------------------------------------------------------
void PushMessagingServiceImpl::Unsubscribe(
const GURL& requesting_origin,
int64_t service_worker_registration_id,
const std::string& sender_id,
const content::PushMessagingService::UnregisterCallback& callback) {
PushMessagingAppIdentifier app_identifier =
PushMessagingAppIdentifier::FindByServiceWorker(
profile_, requesting_origin, service_worker_registration_id);
if (app_identifier.is_null()) {
if (!callback.is_null()) {
callback.Run(
content::PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED);
}
return;
}
Unsubscribe(app_identifier.app_id(), sender_id, callback);
}
void PushMessagingServiceImpl::Unsubscribe(
const std::string& app_id,
const std::string& sender_id,
const content::PushMessagingService::UnregisterCallback& callback) {
// Delete the mapping for this app_id, to guarantee that no messages get
// delivered in future (even if unregistration fails).
// TODO(johnme): Instead of deleting these app ids, store them elsewhere, and
// retry unregistration if it fails due to network errors (crbug.com/465399).
PushMessagingAppIdentifier app_identifier =
PushMessagingAppIdentifier::FindByAppId(profile_, app_id);
bool was_registered = !app_identifier.is_null();
if (was_registered)
app_identifier.DeleteFromPrefs(profile_);
const auto& unregister_callback =
base::Bind(&PushMessagingServiceImpl::DidUnsubscribe,
weak_factory_.GetWeakPtr(), was_registered, callback);
#if defined(OS_ANDROID)
// On Android the backend is different, and requires the original sender_id.
// UnsubscribeBecausePermissionRevoked sometimes calls us with an empty one.
if (sender_id.empty())
unregister_callback.Run(gcm::GCMClient::INVALID_PARAMETER);
else
GetGCMDriver()->UnregisterWithSenderId(app_id, sender_id,
unregister_callback);
#else
GetGCMDriver()->Unregister(app_id, unregister_callback);
#endif
}
void PushMessagingServiceImpl::DidUnsubscribe(
bool was_subscribed,
const content::PushMessagingService::UnregisterCallback& callback,
gcm::GCMClient::Result result) {
if (was_subscribed)
DecreasePushSubscriptionCount(1, false /* was_pending */);
// Internal calls pass a null callback.
if (callback.is_null())
return;
if (!was_subscribed) {
callback.Run(
content::PUSH_UNREGISTRATION_STATUS_SUCCESS_WAS_NOT_REGISTERED);
return;
}
switch (result) {
case gcm::GCMClient::SUCCESS:
callback.Run(content::PUSH_UNREGISTRATION_STATUS_SUCCESS_UNREGISTERED);
break;
case gcm::GCMClient::INVALID_PARAMETER:
case gcm::GCMClient::GCM_DISABLED:
case gcm::GCMClient::SERVER_ERROR:
case gcm::GCMClient::UNKNOWN_ERROR:
callback.Run(content::PUSH_UNREGISTRATION_STATUS_PENDING_SERVICE_ERROR);
break;
case gcm::GCMClient::ASYNC_OPERATION_PENDING:
case gcm::GCMClient::NETWORK_ERROR:
case gcm::GCMClient::TTL_EXCEEDED:
callback.Run(content::PUSH_UNREGISTRATION_STATUS_PENDING_NETWORK_ERROR);
break;
}
}
// OnContentSettingChanged methods ---------------------------------------------
void PushMessagingServiceImpl::OnContentSettingChanged(
const ContentSettingsPattern& primary_pattern,
const ContentSettingsPattern& secondary_pattern,
ContentSettingsType content_type,
std::string resource_identifier) {
if (content_type != CONTENT_SETTINGS_TYPE_PUSH_MESSAGING &&
content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS) {
return;
}
std::vector<PushMessagingAppIdentifier> all_app_identifiers =
PushMessagingAppIdentifier::GetAll(profile_);
base::Closure barrier_closure = base::BarrierClosure(
all_app_identifiers.size(),
content_setting_changed_callback_for_testing_.is_null()
? base::Bind(&base::DoNothing)
: content_setting_changed_callback_for_testing_);
for (const PushMessagingAppIdentifier& app_identifier : all_app_identifiers) {
// If |primary_pattern| is not valid, we should always check for a
// permission change because it can happen for example when the entire
// Push or Notifications permissions are cleared.
// Otherwise, the permission should be checked if the pattern matches the
// origin.
if (primary_pattern.IsValid() &&
!primary_pattern.Matches(app_identifier.origin())) {
barrier_closure.Run();
continue;
}
if (IsPermissionSet(app_identifier.origin())) {
barrier_closure.Run();
continue;
}
GetSenderId(
profile_, app_identifier.origin(),
app_identifier.service_worker_registration_id(),
base::Bind(
&PushMessagingServiceImpl::UnsubscribeBecausePermissionRevoked,
weak_factory_.GetWeakPtr(), app_identifier, barrier_closure));
}
}
void PushMessagingServiceImpl::UnsubscribeBecausePermissionRevoked(
const PushMessagingAppIdentifier& app_identifier,
const base::Closure& closure,
const std::string& sender_id,
bool success,
bool not_found) {
base::Closure barrier_closure = base::BarrierClosure(2, closure);
// Unsubscribe the PushMessagingAppIdentifier with the push service.
// It's possible for GetSenderId to have failed and sender_id to be empty, if
// cookies (and the SW database) for an origin got cleared before permissions
// are cleared for the origin. In that case Unsubscribe will just delete the
// app identifier to block future messages.
// TODO(johnme): Auto-unregister before SW DB is cleared
// (https://crbug.com/402458).
Unsubscribe(app_identifier.app_id(), sender_id,
base::Bind(&UnregisterCallbackToClosure, barrier_closure));
// Clear the associated service worker push registration id.
ClearPushSubscriptionID(profile_, app_identifier.origin(),
app_identifier.service_worker_registration_id(),
barrier_closure);
}
void PushMessagingServiceImpl::SetContentSettingChangedCallbackForTesting(
const base::Closure& callback) {
content_setting_changed_callback_for_testing_ = callback;
}
// KeyedService methods -------------------------------------------------------
void PushMessagingServiceImpl::Shutdown() {
GetGCMDriver()->RemoveAppHandler(kPushMessagingAppIdentifierPrefix);
}
// BackgroundTrigger methods ---------------------------------------------------
base::string16 PushMessagingServiceImpl::GetName() {
return l10n_util::GetStringUTF16(IDS_NOTIFICATIONS_BACKGROUND_SERVICE_NAME);
}
gfx::ImageSkia* PushMessagingServiceImpl::GetIcon() {
return nullptr;
}
void PushMessagingServiceImpl::OnMenuClick() {
#if defined(ENABLE_BACKGROUND)
chrome::ShowContentSettings(
BackgroundModeManager::GetBrowserWindowForProfile(profile_),
CONTENT_SETTINGS_TYPE_NOTIFICATIONS);
#endif // defined(ENABLE_BACKGROUND)
}
// Helper methods --------------------------------------------------------------
// Assumes user_visible always since this is just meant to check
// if the permission was previously granted and not revoked.
bool PushMessagingServiceImpl::IsPermissionSet(const GURL& origin) {
return GetPermissionStatus(origin, origin, true /* user_visible */) ==
blink::WebPushPermissionStatusGranted;
}
bool PushMessagingServiceImpl::AreMessagePayloadsEnabled() const {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kEnableExperimentalWebPlatformFeatures);
}
gcm::GCMDriver* PushMessagingServiceImpl::GetGCMDriver() const {
gcm::GCMProfileService* gcm_profile_service =
gcm::GCMProfileServiceFactory::GetForProfile(profile_);
CHECK(gcm_profile_service);
CHECK(gcm_profile_service->driver());
return gcm_profile_service->driver();
}