summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/push_messaging/push_messaging_service_impl.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/push_messaging/push_messaging_service_impl.cc')
-rw-r--r--chromium/chrome/browser/push_messaging/push_messaging_service_impl.cc1684
1 files changed, 1684 insertions, 0 deletions
diff --git a/chromium/chrome/browser/push_messaging/push_messaging_service_impl.cc b/chromium/chrome/browser/push_messaging/push_messaging_service_impl.cc
new file mode 100644
index 00000000000..6313d399760
--- /dev/null
+++ b/chromium/chrome/browser/push_messaging/push_messaging_service_impl.cc
@@ -0,0 +1,1684 @@
+// 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 <map>
+#include <sstream>
+#include <vector>
+
+#include "base/barrier_closure.h"
+#include "base/base64url.h"
+#include "base/bind.h"
+#include "base/callback_helpers.h"
+#include "base/command_line.h"
+#include "base/feature_list.h"
+#include "base/logging.h"
+#include "base/metrics/histogram_functions.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/strings/string_util.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/time/time.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/content_settings/host_content_settings_map_factory.h"
+#include "chrome/browser/gcm/gcm_profile_service_factory.h"
+#include "chrome/browser/gcm/instance_id/instance_id_profile_service_factory.h"
+#include "chrome/browser/permissions/abusive_origin_permission_revocation_request.h"
+#include "chrome/browser/permissions/permission_manager_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_keep_alive_types.h"
+#include "chrome/browser/profiles/scoped_profile_keep_alive.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_features.h"
+#include "chrome/browser/push_messaging/push_messaging_service_factory.h"
+#include "chrome/browser/push_messaging/push_messaging_utils.h"
+#include "chrome/browser/ui/chrome_pages.h"
+#include "chrome/common/buildflags.h"
+#include "chrome/common/chrome_features.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/gcm_driver/instance_id/instance_id.h"
+#include "components/gcm_driver/instance_id/instance_id_driver.h"
+#include "components/gcm_driver/instance_id/instance_id_profile_service.h"
+#include "components/permissions/permission_manager.h"
+#include "components/permissions/permission_result.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/prefs/pref_service.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/browser/devtools_background_services_context.h"
+#include "content/public/browser/notification_service.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/common/child_process_host.h"
+#include "content/public/common/content_features.h"
+#include "content/public/common/content_switches.h"
+#include "third_party/blink/public/mojom/devtools/console_message.mojom.h"
+#include "third_party/blink/public/mojom/permissions/permission_status.mojom.h"
+#include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
+#include "chrome/browser/background/background_mode_manager.h"
+#include "components/keep_alive_registry/keep_alive_types.h"
+#include "components/keep_alive_registry/scoped_keep_alive.h"
+#endif
+
+#if defined(OS_ANDROID)
+#include "base/android/jni_android.h"
+#include "chrome/android/chrome_jni_headers/PushMessagingServiceObserver_jni.h"
+#endif
+
+using instance_id::InstanceID;
+
+namespace {
+
+// Scope passed to getToken to obtain GCM registration tokens.
+// Must match Java GoogleCloudMessaging.INSTANCE_ID_SCOPE.
+const char kGCMScope[] = "GCM";
+
+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.";
+
+// Message displayed in the console (as an error) when a GCM Sender ID is used
+// to create a subscription, which is unsupported. The subscription request will
+// have been blocked, and an exception will be thrown as well.
+const char kSenderIdRegistrationDisallowedMessage[] =
+ "The provided application server key is not a VAPID key. Only VAPID keys "
+ "are supported. For more information check https://crbug.com/979235.";
+
+// Message displayed in the console (as a warning) when a GCM Sender ID is used
+// to create a subscription, which will soon be unsupported.
+const char kSenderIdRegistrationDeprecatedMessage[] =
+ "The provided application server key is not a VAPID key. Only VAPID keys "
+ "will be supported in the future. For more information check "
+ "https://crbug.com/979235.";
+
+void RecordDeliveryStatus(blink::mojom::PushEventStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PushMessaging.DeliveryStatus", status);
+}
+
+void RecordPushSubcriptionChangeStatus(blink::mojom::PushEventStatus status) {
+ UMA_HISTOGRAM_ENUMERATION("PushMessaging.PushSubscriptionChangeStatus",
+ status);
+}
+void RecordUnsubscribeReason(blink::mojom::PushUnregistrationReason reason) {
+ UMA_HISTOGRAM_ENUMERATION("PushMessaging.UnregistrationReason", reason);
+}
+
+void RecordUnsubscribeGCMResult(gcm::GCMClient::Result result) {
+ UMA_HISTOGRAM_ENUMERATION("PushMessaging.UnregistrationGCMResult", result);
+}
+
+void RecordUnsubscribeIIDResult(InstanceID::Result result) {
+ UMA_HISTOGRAM_ENUMERATION("PushMessaging.UnregistrationIIDResult", result);
+}
+
+blink::mojom::PermissionStatus ToPermissionStatus(
+ ContentSetting content_setting) {
+ switch (content_setting) {
+ case CONTENT_SETTING_ALLOW:
+ return blink::mojom::PermissionStatus::GRANTED;
+ case CONTENT_SETTING_BLOCK:
+ return blink::mojom::PermissionStatus::DENIED;
+ case CONTENT_SETTING_ASK:
+ return blink::mojom::PermissionStatus::ASK;
+ default:
+ break;
+ }
+ NOTREACHED();
+ return blink::mojom::PermissionStatus::DENIED;
+}
+
+void UnregisterCallbackToClosure(
+ base::OnceClosure closure,
+ blink::mojom::PushUnregistrationStatus status) {
+ DCHECK(closure);
+ std::move(closure).Run();
+}
+
+void LogMessageReceivedEventToDevTools(
+ content::DevToolsBackgroundServicesContext* devtools_context,
+ const PushMessagingAppIdentifier& app_identifier,
+ const std::string& message_id,
+ bool was_encrypted,
+ const std::string& error_message,
+ const std::string& payload) {
+ if (!devtools_context)
+ return;
+
+ std::map<std::string, std::string> event_metadata = {
+ {"Success", error_message.empty() ? "Yes" : "No"},
+ {"Was Encrypted", was_encrypted ? "Yes" : "No"}};
+
+ if (!error_message.empty())
+ event_metadata["Error Reason"] = error_message;
+ else if (was_encrypted)
+ event_metadata["Payload"] = payload;
+
+ devtools_context->LogBackgroundServiceEvent(
+ app_identifier.service_worker_registration_id(),
+ url::Origin::Create(app_identifier.origin()),
+ content::DevToolsBackgroundService::kPushMessaging,
+ "Push message received" /* event_name */, message_id, event_metadata);
+}
+
+PendingMessage::PendingMessage(std::string app_id, gcm::IncomingMessage message)
+ : app_id(std::move(app_id)),
+ message(std::move(message)),
+ received_time(base::Time::Now()) {}
+PendingMessage::PendingMessage(const PendingMessage& other) = default;
+PendingMessage::PendingMessage(PendingMessage&& other) = default;
+PendingMessage& PendingMessage::operator=(PendingMessage&& other) = default;
+PendingMessage::~PendingMessage() = default;
+
+} // 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);
+ if (push_service) {
+ push_service->IncreasePushSubscriptionCount(count, false /* is_pending */);
+ push_service->RemoveExpiredSubscriptions();
+ }
+}
+
+void PushMessagingServiceImpl::RemoveExpiredSubscriptions() {
+ if (!base::FeatureList::IsEnabled(
+ features::kPushSubscriptionWithExpirationTime)) {
+ return;
+ }
+
+ base::RepeatingClosure barrier_closure = base::BarrierClosure(
+ PushMessagingAppIdentifier::GetCount(profile_),
+ remove_expired_subscriptions_callback_for_testing_.is_null()
+ ? base::DoNothing()
+ : std::move(remove_expired_subscriptions_callback_for_testing_));
+
+ for (const auto& identifier : PushMessagingAppIdentifier::GetAll(profile_)) {
+ if (!identifier.IsExpired()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, barrier_closure);
+ continue;
+ }
+ content::BrowserThread::PostBestEffortTask(
+ FROM_HERE, base::ThreadTaskRunnerHandle::Get(),
+ base::BindOnce(
+ &PushMessagingServiceImpl::UnexpectedChange,
+ weak_factory_.GetWeakPtr(), identifier,
+ blink::mojom::PushUnregistrationReason::SUBSCRIPTION_EXPIRED,
+ barrier_closure));
+ }
+}
+
+void PushMessagingServiceImpl::UnexpectedChange(
+ PushMessagingAppIdentifier identifier,
+ blink::mojom::PushUnregistrationReason reason,
+ base::OnceClosure completed_closure) {
+ auto unsubscribe_closure =
+ base::BindOnce(&PushMessagingServiceImpl::UnexpectedUnsubscribe,
+ weak_factory_.GetWeakPtr(), identifier, reason,
+ base::BindOnce(&UnregisterCallbackToClosure,
+ std::move(completed_closure)));
+ if (base::FeatureList::IsEnabled(features::kPushSubscriptionChangeEvent)) {
+ // Find old subscription and fire a `pushsubscriptionchange` event
+ GetPushSubscriptionFromAppIdentifier(
+ identifier,
+ base::BindOnce(&PushMessagingServiceImpl::FirePushSubscriptionChange,
+ weak_factory_.GetWeakPtr(), identifier,
+ std::move(unsubscribe_closure),
+ nullptr /* new_subscription */));
+ } else {
+ std::move(unsubscribe_closure).Run();
+ }
+}
+
+PushMessagingServiceImpl::PushMessagingServiceImpl(Profile* profile)
+ : profile_(profile),
+ push_subscription_count_(0),
+ pending_push_subscription_count_(0),
+ notification_manager_(profile) {
+ DCHECK(profile);
+ HostContentSettingsMapFactory::GetForProfile(profile_)->AddObserver(this);
+
+ registrar_.Add(this, chrome::NOTIFICATION_APP_TERMINATING,
+ content::NotificationService::AllSources());
+ refresh_observation_.Observe(&refresher_);
+}
+
+PushMessagingServiceImpl::~PushMessagingServiceImpl() = default;
+
+void PushMessagingServiceImpl::IncreasePushSubscriptionCount(int add,
+ bool is_pending) {
+ DCHECK_GT(add, 0);
+ if (push_subscription_count_ + pending_push_subscription_count_ == 0)
+ GetGCMDriver()->AddAppHandler(kPushMessagingAppIdentifierPrefix, this);
+
+ if (is_pending)
+ pending_push_subscription_count_ += add;
+ else
+ push_subscription_count_ += add;
+}
+
+void PushMessagingServiceImpl::DecreasePushSubscriptionCount(int subtract,
+ bool was_pending) {
+ DCHECK_GT(subtract, 0);
+ if (was_pending) {
+ pending_push_subscription_count_ -= subtract;
+ DCHECK_GE(pending_push_subscription_count_, 0);
+ } else {
+ push_subscription_count_ -= subtract;
+ DCHECK_GE(push_subscription_count_, 0);
+ }
+
+ if (push_subscription_count_ + pending_push_subscription_count_ == 0)
+ GetGCMDriver()->RemoveAppHandler(kPushMessagingAppIdentifierPrefix);
+}
+
+bool PushMessagingServiceImpl::CanHandle(const std::string& app_id) const {
+ return base::StartsWith(app_id, kPushMessagingAppIdentifierPrefix,
+ base::CompareCase::INSENSITIVE_ASCII);
+}
+
+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();
+}
+
+void PushMessagingServiceImpl::OnStoreReset() {
+ // Delete all cached subscriptions, since they are now invalid.
+ for (const auto& identifier : PushMessagingAppIdentifier::GetAll(profile_)) {
+ RecordUnsubscribeReason(
+ blink::mojom::PushUnregistrationReason::GCM_STORE_RESET);
+ // Clear all the subscriptions in parallel, to reduce risk that shutdown
+ // occurs before we finish clearing them.
+ ClearPushSubscriptionId(profile_, identifier.origin(),
+ identifier.service_worker_registration_id(),
+ base::DoNothing());
+ // TODO(johnme): Fire pushsubscriptionchange/pushsubscriptionlost SW event.
+ }
+ PushMessagingAppIdentifier::DeleteAllFromPrefs(profile_);
+}
+
+// OnMessage methods -----------------------------------------------------------
+
+void PushMessagingServiceImpl::OnMessage(const std::string& app_id,
+ const gcm::IncomingMessage& message) {
+ // We won't have time to process and act on the message.
+ // TODO(peter) This should be checked at the level of the GCMDriver, so that
+ // the message is not consumed. See https://crbug.com/612815
+ if (g_browser_process->IsShuttingDown() || shutdown_started_)
+ return;
+
+#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
+ if (g_browser_process->background_mode_manager()) {
+ UMA_HISTOGRAM_BOOLEAN("PushMessaging.ReceivedMessageInBackground",
+ g_browser_process->background_mode_manager()
+ ->IsBackgroundWithoutWindows());
+ }
+
+ if (!in_flight_keep_alive_) {
+ in_flight_keep_alive_ = std::make_unique<ScopedKeepAlive>(
+ KeepAliveOrigin::IN_FLIGHT_PUSH_MESSAGE,
+ KeepAliveRestartOption::DISABLED);
+ in_flight_profile_keep_alive_ = std::make_unique<ScopedProfileKeepAlive>(
+ profile_, ProfileKeepAliveOrigin::kInFlightPushMessage);
+ }
+#endif
+
+ refresher_.GotMessageFrom(app_id);
+
+ 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()) {
+ absl::optional<PushMessagingAppIdentifier> refresh_identifier =
+ refresher_.FindActiveAppIdentifier(app_id);
+ if (!refresh_identifier) {
+ DeliverMessageCallback(app_id, GURL::EmptyGURL(),
+ -1 /* kInvalidServiceWorkerRegistrationId */,
+ message, false /* did_enqueue_message */,
+ blink::mojom::PushEventStatus::UNKNOWN_APP_ID);
+ return;
+ }
+ app_identifier = std::move(*refresh_identifier);
+ }
+
+ LogMessageReceivedEventToDevTools(
+ GetDevToolsContext(app_identifier.origin()), app_identifier,
+ message.message_id,
+ /* was_encrypted= */ message.decrypted, std::string() /* error_message */,
+ message.decrypted ? message.raw_data : std::string());
+
+ if (IsPermissionSet(app_identifier.origin())) {
+ messages_pending_permission_check_.emplace(app_id, message);
+ // Start abusive origin verification only if no other verification is in
+ // progress.
+ if (!abusive_origin_revocation_request_)
+ CheckOriginForAbuseAndDispatchNextMessage();
+ } else {
+ // Drop message and unregister if origin has lost push permission.
+ DeliverMessageCallback(app_id, app_identifier.origin(),
+ app_identifier.service_worker_registration_id(),
+ message, false /* did_enqueue_message */,
+ blink::mojom::PushEventStatus::PERMISSION_DENIED);
+ }
+}
+
+void PushMessagingServiceImpl::CheckOriginForAbuseAndDispatchNextMessage() {
+ if (messages_pending_permission_check_.empty())
+ return;
+
+ PendingMessage message =
+ std::move(messages_pending_permission_check_.front());
+ messages_pending_permission_check_.pop();
+
+ PushMessagingAppIdentifier app_identifier =
+ PushMessagingAppIdentifier::FindByAppId(profile_, message.app_id);
+
+ if (app_identifier.is_null()) {
+ CheckOriginForAbuseAndDispatchNextMessage();
+ return;
+ }
+
+ DCHECK(!abusive_origin_revocation_request_)
+ << "Create one Abusive Origin Revocation instance per request.";
+ abusive_origin_revocation_request_ =
+ std::make_unique<AbusiveOriginPermissionRevocationRequest>(
+ profile_, app_identifier.origin(),
+ base::BindOnce(&PushMessagingServiceImpl::OnCheckedOriginForAbuse,
+ weak_factory_.GetWeakPtr(), std::move(message)));
+}
+
+void PushMessagingServiceImpl::OnCheckedOriginForAbuse(
+ PendingMessage message,
+ AbusiveOriginPermissionRevocationRequest::Outcome outcome) {
+ abusive_origin_revocation_request_.reset();
+
+ base::UmaHistogramLongTimes("PushMessaging.CheckOriginForAbuseTime",
+ base::Time::Now() - message.received_time);
+
+ PushMessagingAppIdentifier app_identifier =
+ PushMessagingAppIdentifier::FindByAppId(profile_, message.app_id);
+
+ if (app_identifier.is_null()) {
+ CheckOriginForAbuseAndDispatchNextMessage();
+ return;
+ }
+
+ const GURL& origin = app_identifier.origin();
+ int64_t service_worker_registration_id =
+ app_identifier.service_worker_registration_id();
+
+ // It is possible that Notifications permission has been revoked by an user
+ // during abusive origin verification.
+ if (outcome == AbusiveOriginPermissionRevocationRequest::Outcome::
+ PERMISSION_NOT_REVOKED &&
+ IsPermissionSet(origin)) {
+ std::queue<PendingMessage>& delivery_queue =
+ message_delivery_queue_[{origin, service_worker_registration_id}];
+ delivery_queue.push(std::move(message));
+
+ // Start delivering push messages to this service worker if this was the
+ // first message. Otherwise just enqueue the message to be delivered once
+ // all previous messages have been handled.
+ if (delivery_queue.size() == 1) {
+ DeliverNextQueuedMessageForServiceWorkerRegistration(
+ origin, service_worker_registration_id);
+ }
+ } else {
+ // Drop message and unregister if origin has lost push permission.
+ DeliverMessageCallback(
+ message.app_id, origin, service_worker_registration_id, message.message,
+ false /* did_enqueue_message */,
+ outcome == AbusiveOriginPermissionRevocationRequest::Outcome::
+ PERMISSION_NOT_REVOKED
+ ? blink::mojom::PushEventStatus::PERMISSION_DENIED
+ : blink::mojom::PushEventStatus::PERMISSION_REVOKED_ABUSIVE);
+ }
+
+ // Verify the next message in the queue.
+ CheckOriginForAbuseAndDispatchNextMessage();
+}
+
+void PushMessagingServiceImpl::
+ DeliverNextQueuedMessageForServiceWorkerRegistration(
+ const GURL& origin,
+ int64_t service_worker_registration_id) {
+ MessageDeliveryQueueKey key{origin, service_worker_registration_id};
+ auto iter = message_delivery_queue_.find(key);
+ if (iter == message_delivery_queue_.end())
+ return;
+
+ const std::queue<PendingMessage>& delivery_queue = iter->second;
+ CHECK(!delivery_queue.empty());
+ const PendingMessage& next_message = delivery_queue.front();
+
+ const std::string& app_id = next_message.app_id;
+ const gcm::IncomingMessage& message = next_message.message;
+
+ auto deliver_message_callback = base::BindOnce(
+ &PushMessagingServiceImpl::DeliverMessageCallback,
+ weak_factory_.GetWeakPtr(), app_id, origin,
+ service_worker_registration_id, message, true /* did_enqueue_message */);
+
+ // It is possible that Notification permissions have been revoked by a user
+ // while handling previous messages for |origin|.
+ if (!IsPermissionSet(origin)) {
+ std::move(deliver_message_callback)
+ .Run(blink::mojom::PushEventStatus::PERMISSION_DENIED);
+ return;
+ }
+
+ // The payload of a push message can be valid with content, valid with empty
+ // content, or null.
+ absl::optional<std::string> payload;
+ if (message.decrypted)
+ payload = message.raw_data;
+
+ base::UmaHistogramLongTimes("PushMessaging.DeliverQueuedMessageTime",
+ base::Time::Now() - next_message.received_time);
+
+ // Inform tests observing message dispatching about the event.
+ if (message_dispatched_callback_for_testing_) {
+ message_dispatched_callback_for_testing_.Run(
+ app_id, origin, service_worker_registration_id, std::move(payload),
+ std::move(deliver_message_callback));
+ return;
+ }
+
+ // Dispatch the message to the appropriate Service Worker.
+ profile_->DeliverPushMessage(origin, service_worker_registration_id,
+ message.message_id, payload,
+ std::move(deliver_message_callback));
+}
+
+void PushMessagingServiceImpl::DeliverMessageCallback(
+ const std::string& app_id,
+ const GURL& requesting_origin,
+ int64_t service_worker_registration_id,
+ const gcm::IncomingMessage& message,
+ bool did_enqueue_message,
+ blink::mojom::PushEventStatus status) {
+ RecordDeliveryStatus(status);
+
+ // Note: It's important that |message_handled_callback| is run or passed to
+ // another function before this function returns.
+ auto message_handled_callback =
+ base::BindOnce(&PushMessagingServiceImpl::DidHandleMessage,
+ weak_factory_.GetWeakPtr(), app_id, message.message_id);
+
+ if (did_enqueue_message) {
+ message_handled_callback = base::BindOnce(
+ &PushMessagingServiceImpl::DidHandleEnqueuedMessage,
+ weak_factory_.GetWeakPtr(), requesting_origin,
+ service_worker_registration_id, std::move(message_handled_callback));
+ }
+
+ // A reason to automatically unsubscribe. UNKNOWN means do not unsubscribe.
+ blink::mojom::PushUnregistrationReason unsubscribe_reason =
+ blink::mojom::PushUnregistrationReason::UNKNOWN;
+
+ // 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 blink::mojom::PushEventStatus::SUCCESS:
+ case blink::mojom::PushEventStatus::EVENT_WAITUNTIL_REJECTED:
+ case blink::mojom::PushEventStatus::TIMEOUT:
+ // Only enforce the user visible requirements if silent push has not been
+ // enabled through a command line flag.
+ if (!base::CommandLine::ForCurrentProcess()->HasSwitch(
+ switches::kAllowSilentPush)) {
+ notification_manager_.EnforceUserVisibleOnlyRequirements(
+ requesting_origin, service_worker_registration_id,
+ std::move(message_handled_callback));
+ message_handled_callback = base::OnceCallback<void(bool)>();
+ }
+ break;
+ case blink::mojom::PushEventStatus::SERVICE_WORKER_ERROR:
+ // Do nothing, and hope the error is transient.
+ break;
+ case blink::mojom::PushEventStatus::UNKNOWN_APP_ID:
+ unsubscribe_reason =
+ blink::mojom::PushUnregistrationReason::DELIVERY_UNKNOWN_APP_ID;
+ break;
+ case blink::mojom::PushEventStatus::PERMISSION_DENIED:
+ unsubscribe_reason =
+ blink::mojom::PushUnregistrationReason::DELIVERY_PERMISSION_DENIED;
+ break;
+ case blink::mojom::PushEventStatus::NO_SERVICE_WORKER:
+ unsubscribe_reason =
+ blink::mojom::PushUnregistrationReason::DELIVERY_NO_SERVICE_WORKER;
+ break;
+ case blink::mojom::PushEventStatus::PERMISSION_REVOKED_ABUSIVE:
+ unsubscribe_reason =
+ blink::mojom::PushUnregistrationReason::PERMISSION_REVOKED_ABUSIVE;
+ break;
+ }
+
+ // If |message_handled_callback| was not yet used, make a
+ // |completion_closure_runner| which should run by default at the end of this
+ // function, unless it is explicitly passed to another function or disabled.
+ base::ScopedClosureRunner completion_closure_runner(
+ message_handled_callback
+ ? base::BindOnce(std::move(message_handled_callback),
+ false /* did_show_generic_notification */)
+ : base::DoNothing());
+
+ if (unsubscribe_reason != blink::mojom::PushUnregistrationReason::UNKNOWN) {
+ PushMessagingAppIdentifier app_identifier =
+ PushMessagingAppIdentifier::FindByAppId(profile_, app_id);
+ UnsubscribeInternal(
+ unsubscribe_reason,
+ app_identifier.is_null() ? GURL::EmptyGURL() : app_identifier.origin(),
+ app_identifier.is_null()
+ ? -1 /* kInvalidServiceWorkerRegistrationId */
+ : app_identifier.service_worker_registration_id(),
+ app_id, message.sender_id,
+ base::BindOnce(&UnregisterCallbackToClosure,
+ completion_closure_runner.Release()));
+
+ if (app_identifier.is_null())
+ return;
+
+ if (auto* devtools_context = GetDevToolsContext(app_identifier.origin())) {
+ std::stringstream ss;
+ ss << unsubscribe_reason;
+ devtools_context->LogBackgroundServiceEvent(
+ app_identifier.service_worker_registration_id(),
+ url::Origin::Create(app_identifier.origin()),
+ content::DevToolsBackgroundService::kPushMessaging,
+ "Unsubscribed due to error" /* event_name */, message.message_id,
+ {{"Reason", ss.str()}});
+ }
+ }
+}
+
+void PushMessagingServiceImpl::DidHandleEnqueuedMessage(
+ const GURL& origin,
+ int64_t service_worker_registration_id,
+ base::OnceCallback<void(bool)> message_handled_callback,
+ bool did_show_generic_notification) {
+ // Lookup the message queue for the correct service worker.
+ MessageDeliveryQueueKey key{origin, service_worker_registration_id};
+ auto iter = message_delivery_queue_.find(key);
+ CHECK(iter != message_delivery_queue_.end());
+
+ // Remove the delivered message from the queue.
+ std::queue<PendingMessage>& delivery_queue = iter->second;
+ CHECK(!delivery_queue.empty());
+
+ base::UmaHistogramLongTimes(
+ "PushMessaging.MessageHandledTime",
+ base::Time::Now() - delivery_queue.front().received_time);
+
+ delivery_queue.pop();
+ if (delivery_queue.empty())
+ message_delivery_queue_.erase(key);
+
+ // This will call PushMessagingServiceImpl::DidHandleMessage().
+ std::move(message_handled_callback).Run(did_show_generic_notification);
+
+ // Deliver next message to this service worker now. We deliver them in series
+ // so we can check the visibility requirements after each message.
+ DeliverNextQueuedMessageForServiceWorkerRegistration(
+ origin, service_worker_registration_id);
+}
+
+void PushMessagingServiceImpl::DidHandleMessage(
+ const std::string& app_id,
+ const std::string& push_message_id,
+ bool did_show_generic_notification) {
+#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
+ // Reset before running callbacks below, so tests can verify keep-alive reset.
+ if (message_delivery_queue_.empty()) {
+ in_flight_keep_alive_.reset();
+ in_flight_profile_keep_alive_.reset();
+ }
+#endif
+
+ if (message_callback_for_testing_)
+ message_callback_for_testing_.Run();
+
+#if defined(OS_ANDROID)
+ chrome::android::Java_PushMessagingServiceObserver_onMessageHandled(
+ base::android::AttachCurrentThread());
+#endif
+
+ PushMessagingAppIdentifier app_identifier =
+ PushMessagingAppIdentifier::FindByAppId(profile_, app_id);
+
+ if (app_identifier.is_null() || !did_show_generic_notification)
+ return;
+
+ if (auto* devtools_context = GetDevToolsContext(app_identifier.origin())) {
+ devtools_context->LogBackgroundServiceEvent(
+ app_identifier.service_worker_registration_id(),
+ url::Origin::Create(app_identifier.origin()),
+ content::DevToolsBackgroundService::kPushMessaging,
+ "Generic notification shown" /* event_name */, push_message_id,
+ {} /* event_metadata */);
+ }
+}
+
+void PushMessagingServiceImpl::SetMessageCallbackForTesting(
+ const base::RepeatingClosure& 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";
+}
+
+void PushMessagingServiceImpl::OnMessageDecryptionFailed(
+ const std::string& app_id,
+ const std::string& message_id,
+ const std::string& error_message) {
+ PushMessagingAppIdentifier app_identifier =
+ PushMessagingAppIdentifier::FindByAppId(profile_, app_id);
+
+ if (app_identifier.is_null())
+ return;
+
+ LogMessageReceivedEventToDevTools(
+ GetDevToolsContext(app_identifier.origin()), app_identifier, message_id,
+ /* was_encrypted= */ true, error_message, "" /* payload */);
+}
+
+// Subscribe and GetPermissionStatus methods -----------------------------------
+
+void PushMessagingServiceImpl::SubscribeFromDocument(
+ const GURL& requesting_origin,
+ int64_t service_worker_registration_id,
+ int render_process_id,
+ int render_frame_id,
+ blink::mojom::PushSubscriptionOptionsPtr options,
+ bool user_gesture,
+ RegisterCallback callback) {
+ PushMessagingAppIdentifier app_identifier =
+ PushMessagingAppIdentifier::FindByServiceWorker(
+ profile_, requesting_origin, service_worker_registration_id);
+
+ // If there is no existing app identifier for the given Service Worker,
+ // generate a new one. This will create a new subscription on the server.
+ if (app_identifier.is_null()) {
+ app_identifier = PushMessagingAppIdentifier::Generate(
+ requesting_origin, service_worker_registration_id);
+ }
+
+ if (push_subscription_count_ + pending_push_subscription_count_ >=
+ kMaxRegistrations) {
+ SubscribeEndWithError(std::move(callback),
+ blink::mojom::PushRegistrationStatus::LIMIT_REACHED);
+ return;
+ }
+
+ content::RenderFrameHost* render_frame_host =
+ content::RenderFrameHost::FromID(render_process_id, render_frame_id);
+
+ if (!render_frame_host) {
+ // It is possible for `render_frame_host` to be nullptr here due to a race
+ // (crbug.com/1057981).
+ SubscribeEndWithError(
+ std::move(callback),
+ blink::mojom::PushRegistrationStatus::RENDERER_SHUTDOWN);
+ return;
+ }
+
+ if (!options->user_visible_only) {
+ render_frame_host->AddMessageToConsole(
+ blink::mojom::ConsoleMessageLevel::kError,
+ kSilentPushUnsupportedMessage);
+
+ SubscribeEndWithError(
+ std::move(callback),
+ blink::mojom::PushRegistrationStatus::PERMISSION_DENIED);
+ return;
+ }
+
+ // Push does not allow permission requests from iframes.
+ PermissionManagerFactory::GetForProfile(profile_)->RequestPermission(
+ ContentSettingsType::NOTIFICATIONS, render_frame_host, requesting_origin,
+ user_gesture,
+ base::BindOnce(&PushMessagingServiceImpl::DoSubscribe,
+ weak_factory_.GetWeakPtr(), std::move(app_identifier),
+ std::move(options), std::move(callback), render_process_id,
+ render_frame_id));
+}
+
+void PushMessagingServiceImpl::SubscribeFromWorker(
+ const GURL& requesting_origin,
+ int64_t service_worker_registration_id,
+ blink::mojom::PushSubscriptionOptionsPtr options,
+ RegisterCallback register_callback) {
+ PushMessagingAppIdentifier app_identifier =
+ PushMessagingAppIdentifier::FindByServiceWorker(
+ profile_, requesting_origin, service_worker_registration_id);
+
+ // If there is no existing app identifier for the given Service Worker,
+ // generate a new one. This will create a new subscription on the server.
+ if (app_identifier.is_null()) {
+ app_identifier = PushMessagingAppIdentifier::Generate(
+ requesting_origin, service_worker_registration_id);
+ }
+
+ if (push_subscription_count_ + pending_push_subscription_count_ >=
+ kMaxRegistrations) {
+ SubscribeEndWithError(std::move(register_callback),
+ blink::mojom::PushRegistrationStatus::LIMIT_REACHED);
+ return;
+ }
+
+ if (!IsPermissionSet(requesting_origin, options->user_visible_only)) {
+ SubscribeEndWithError(
+ std::move(register_callback),
+ blink::mojom::PushRegistrationStatus::PERMISSION_DENIED);
+ return;
+ }
+
+ DoSubscribe(std::move(app_identifier), std::move(options),
+ std::move(register_callback),
+ /* render_process_id= */ -1, /* render_frame_id= */ -1,
+ CONTENT_SETTING_ALLOW);
+}
+
+blink::mojom::PermissionStatus PushMessagingServiceImpl::GetPermissionStatus(
+ const GURL& origin,
+ bool user_visible) {
+ if (!user_visible)
+ return blink::mojom::PermissionStatus::DENIED;
+
+ // Because the Push API is tied to Service Workers, many usages of the API
+ // won't have an embedding origin at all. Only consider the requesting
+ // |origin| when checking whether permission to use the API has been granted.
+ return ToPermissionStatus(
+ PermissionManagerFactory::GetForProfile(profile_)
+ ->GetPermissionStatus(ContentSettingsType::NOTIFICATIONS, origin,
+ origin)
+ .content_setting);
+}
+
+bool PushMessagingServiceImpl::SupportNonVisibleMessages() {
+ return false;
+}
+
+void PushMessagingServiceImpl::DoSubscribe(
+ PushMessagingAppIdentifier app_identifier,
+ blink::mojom::PushSubscriptionOptionsPtr options,
+ RegisterCallback register_callback,
+ int render_process_id,
+ int render_frame_id,
+ ContentSetting content_setting) {
+ if (content_setting != CONTENT_SETTING_ALLOW) {
+ SubscribeEndWithError(
+ std::move(register_callback),
+ blink::mojom::PushRegistrationStatus::PERMISSION_DENIED);
+ return;
+ }
+
+ std::string application_server_key_string(
+ options->application_server_key.begin(),
+ options->application_server_key.end());
+
+ // TODO(peter): Move this check to the renderer process & Mojo message
+ // validation once the flag is always enabled, and remove the
+ // |render_process_id| and |render_frame_id| parameters from this method.
+ if (!push_messaging::IsVapidKey(application_server_key_string)) {
+ content::RenderFrameHost* render_frame_host =
+ content::RenderFrameHost::FromID(render_process_id, render_frame_id);
+ if (base::FeatureList::IsEnabled(
+ features::kPushMessagingDisallowSenderIDs)) {
+ if (render_frame_host) {
+ render_frame_host->AddMessageToConsole(
+ blink::mojom::ConsoleMessageLevel::kError,
+ kSenderIdRegistrationDisallowedMessage);
+ }
+ SubscribeEndWithError(
+ std::move(register_callback),
+ blink::mojom::PushRegistrationStatus::UNSUPPORTED_GCM_SENDER_ID);
+ return;
+ } else if (render_frame_host) {
+ render_frame_host->AddMessageToConsole(
+ blink::mojom::ConsoleMessageLevel::kWarning,
+ kSenderIdRegistrationDeprecatedMessage);
+ }
+ }
+
+ IncreasePushSubscriptionCount(1, true /* is_pending */);
+
+ // Set time to live for GCM registration
+ base::TimeDelta ttl = base::TimeDelta();
+
+ if (base::FeatureList::IsEnabled(
+ features::kPushSubscriptionWithExpirationTime)) {
+ app_identifier.set_expiration_time(
+ base::Time::Now() + kPushSubscriptionExpirationPeriodTimeDelta);
+ DCHECK(app_identifier.expiration_time());
+ ttl = kPushSubscriptionExpirationPeriodTimeDelta;
+ }
+
+ GetInstanceIDDriver()
+ ->GetInstanceID(app_identifier.app_id())
+ ->GetToken(
+ push_messaging::NormalizeSenderInfo(application_server_key_string),
+ kGCMScope, ttl, {} /* flags */,
+ base::BindOnce(&PushMessagingServiceImpl::DidSubscribe,
+ weak_factory_.GetWeakPtr(), app_identifier,
+ application_server_key_string,
+ std::move(register_callback)));
+}
+
+void PushMessagingServiceImpl::SubscribeEnd(
+ RegisterCallback callback,
+ const std::string& subscription_id,
+ const GURL& endpoint,
+ const absl::optional<base::Time>& expiration_time,
+ const std::vector<uint8_t>& p256dh,
+ const std::vector<uint8_t>& auth,
+ blink::mojom::PushRegistrationStatus status) {
+ std::move(callback).Run(subscription_id, endpoint, expiration_time, p256dh,
+ auth, status);
+}
+
+void PushMessagingServiceImpl::SubscribeEndWithError(
+ RegisterCallback callback,
+ blink::mojom::PushRegistrationStatus status) {
+ SubscribeEnd(std::move(callback), std::string() /* subscription_id */,
+ GURL::EmptyGURL() /* endpoint */,
+ absl::nullopt /* expiration_time */,
+ std::vector<uint8_t>() /* p256dh */,
+ std::vector<uint8_t>() /* auth */, status);
+}
+
+void PushMessagingServiceImpl::DidSubscribe(
+ const PushMessagingAppIdentifier& app_identifier,
+ const std::string& sender_id,
+ RegisterCallback callback,
+ const std::string& subscription_id,
+ InstanceID::Result result) {
+ DecreasePushSubscriptionCount(1, true /* was_pending */);
+
+ blink::mojom::PushRegistrationStatus status =
+ blink::mojom::PushRegistrationStatus::SERVICE_ERROR;
+
+ switch (result) {
+ case InstanceID::SUCCESS: {
+ const GURL endpoint = push_messaging::CreateEndpoint(subscription_id);
+
+ // 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.
+ GetEncryptionInfoForAppId(
+ app_identifier.app_id(), sender_id,
+ base::BindOnce(
+ &PushMessagingServiceImpl::DidSubscribeWithEncryptionInfo,
+ weak_factory_.GetWeakPtr(), app_identifier, std::move(callback),
+ subscription_id, endpoint));
+ return;
+ }
+ case InstanceID::INVALID_PARAMETER:
+ case InstanceID::DISABLED:
+ case InstanceID::ASYNC_OPERATION_PENDING:
+ case InstanceID::SERVER_ERROR:
+ case InstanceID::UNKNOWN_ERROR:
+ DLOG(ERROR) << "Push messaging subscription failed; InstanceID::Result = "
+ << result;
+ status = blink::mojom::PushRegistrationStatus::SERVICE_ERROR;
+ break;
+ case InstanceID::NETWORK_ERROR:
+ status = blink::mojom::PushRegistrationStatus::NETWORK_ERROR;
+ break;
+ }
+
+ SubscribeEndWithError(std::move(callback), status);
+}
+
+void PushMessagingServiceImpl::DidSubscribeWithEncryptionInfo(
+ const PushMessagingAppIdentifier& app_identifier,
+ RegisterCallback callback,
+ const std::string& subscription_id,
+ const GURL& endpoint,
+ std::string p256dh,
+ std::string auth_secret) {
+ if (p256dh.empty()) {
+ SubscribeEndWithError(
+ std::move(callback),
+ blink::mojom::PushRegistrationStatus::PUBLIC_KEY_UNAVAILABLE);
+ return;
+ }
+
+ app_identifier.PersistToPrefs(profile_);
+
+ IncreasePushSubscriptionCount(1, false /* is_pending */);
+
+ SubscribeEnd(std::move(callback), subscription_id, endpoint,
+ app_identifier.expiration_time(),
+ std::vector<uint8_t>(p256dh.begin(), p256dh.end()),
+ std::vector<uint8_t>(auth_secret.begin(), auth_secret.end()),
+ blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE);
+}
+
+// GetSubscriptionInfo methods -------------------------------------------------
+
+void PushMessagingServiceImpl::GetSubscriptionInfo(
+ const GURL& origin,
+ int64_t service_worker_registration_id,
+ const std::string& sender_id,
+ const std::string& subscription_id,
+ SubscriptionInfoCallback callback) {
+ PushMessagingAppIdentifier app_identifier =
+ PushMessagingAppIdentifier::FindByServiceWorker(
+ profile_, origin, service_worker_registration_id);
+
+ if (app_identifier.is_null()) {
+ std::move(callback).Run(
+ false /* is_valid */, GURL::EmptyGURL() /*endpoint*/,
+ absl::nullopt /* expiration_time */,
+ std::vector<uint8_t>() /* p256dh */, std::vector<uint8_t>() /* auth */);
+ return;
+ }
+
+ const GURL endpoint = push_messaging::CreateEndpoint(subscription_id);
+ const std::string& app_id = app_identifier.app_id();
+ absl::optional<base::Time> expiration_time = app_identifier.expiration_time();
+
+ base::OnceCallback<void(bool)> validate_cb =
+ base::BindOnce(&PushMessagingServiceImpl::DidValidateSubscription,
+ weak_factory_.GetWeakPtr(), app_id, sender_id, endpoint,
+ expiration_time, std::move(callback));
+
+ if (PushMessagingAppIdentifier::UseInstanceID(app_id)) {
+ GetInstanceIDDriver()->GetInstanceID(app_id)->ValidateToken(
+ push_messaging::NormalizeSenderInfo(sender_id), kGCMScope,
+ subscription_id, std::move(validate_cb));
+ } else {
+ GetGCMDriver()->ValidateRegistration(
+ app_id, {push_messaging::NormalizeSenderInfo(sender_id)},
+ subscription_id, std::move(validate_cb));
+ }
+}
+
+void PushMessagingServiceImpl::DidValidateSubscription(
+ const std::string& app_id,
+ const std::string& sender_id,
+ const GURL& endpoint,
+ const absl::optional<base::Time>& expiration_time,
+ SubscriptionInfoCallback callback,
+ bool is_valid) {
+ if (!is_valid) {
+ std::move(callback).Run(
+ false /* is_valid */, GURL::EmptyGURL() /* endpoint */,
+ absl::nullopt /* expiration_time */,
+ std::vector<uint8_t>() /* p256dh */, std::vector<uint8_t>() /* auth */);
+ return;
+ }
+
+ GetEncryptionInfoForAppId(
+ app_id, sender_id,
+ base::BindOnce(&PushMessagingServiceImpl::DidGetEncryptionInfo,
+ weak_factory_.GetWeakPtr(), endpoint, expiration_time,
+ std::move(callback)));
+}
+
+void PushMessagingServiceImpl::DidGetEncryptionInfo(
+ const GURL& endpoint,
+ const absl::optional<base::Time>& expiration_time,
+ SubscriptionInfoCallback callback,
+ std::string p256dh,
+ std::string auth_secret) const {
+ // I/O errors might prevent the GCM Driver from retrieving a key-pair.
+ bool is_valid = !p256dh.empty();
+ std::move(callback).Run(
+ is_valid, endpoint, expiration_time,
+ std::vector<uint8_t>(p256dh.begin(), p256dh.end()),
+ std::vector<uint8_t>(auth_secret.begin(), auth_secret.end()));
+}
+
+// Unsubscribe methods ---------------------------------------------------------
+
+void PushMessagingServiceImpl::Unsubscribe(
+ blink::mojom::PushUnregistrationReason reason,
+ const GURL& requesting_origin,
+ int64_t service_worker_registration_id,
+ const std::string& sender_id,
+ UnregisterCallback callback) {
+ PushMessagingAppIdentifier app_identifier =
+ PushMessagingAppIdentifier::FindByServiceWorker(
+ profile_, requesting_origin, service_worker_registration_id);
+
+ UnsubscribeInternal(
+ reason, requesting_origin, service_worker_registration_id,
+ app_identifier.is_null() ? std::string() : app_identifier.app_id(),
+ sender_id, std::move(callback));
+}
+
+void PushMessagingServiceImpl::UnsubscribeInternal(
+ blink::mojom::PushUnregistrationReason reason,
+ const GURL& origin,
+ int64_t service_worker_registration_id,
+ const std::string& app_id,
+ const std::string& sender_id,
+ UnregisterCallback callback) {
+ DCHECK(!app_id.empty() || (!origin.is_empty() &&
+ service_worker_registration_id !=
+ -1 /* kInvalidServiceWorkerRegistrationId */))
+ << "Need an app_id and/or origin+service_worker_registration_id";
+
+ RecordUnsubscribeReason(reason);
+
+ if (origin.is_empty() ||
+ service_worker_registration_id ==
+ -1 /* kInvalidServiceWorkerRegistrationId */) {
+ // Can't clear Service Worker database.
+ DidClearPushSubscriptionId(reason, app_id, sender_id, std::move(callback));
+ return;
+ }
+ ClearPushSubscriptionId(
+ profile_, origin, service_worker_registration_id,
+ base::BindOnce(&PushMessagingServiceImpl::DidClearPushSubscriptionId,
+ weak_factory_.GetWeakPtr(), reason, app_id, sender_id,
+ std::move(callback)));
+}
+
+void PushMessagingServiceImpl::DidClearPushSubscriptionId(
+ blink::mojom::PushUnregistrationReason reason,
+ const std::string& app_id,
+ const std::string& sender_id,
+ UnregisterCallback callback) {
+ if (app_id.empty()) {
+ // Without an |app_id|, we can neither delete the subscription from the
+ // PushMessagingAppIdentifier map, nor unsubscribe with the GCM Driver.
+ std::move(callback).Run(
+ blink::mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED);
+ return;
+ }
+
+ // 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_subscribed = !app_identifier.is_null();
+ if (was_subscribed)
+ app_identifier.DeleteFromPrefs(profile_);
+
+ // Run the unsubscribe callback *before* asking the InstanceIDDriver/GCMDriver
+ // to unsubscribe, since that's a slow process involving network retries, and
+ // by this point enough local state has been deleted that the subscription is
+ // inactive. Note that DeliverMessageCallback automatically unsubscribes if
+ // messages are later received for a subscription that was locally deleted,
+ // so as long as messages keep getting sent to it, the unsubscription should
+ // eventually reach GCM servers even if this particular attempt fails.
+ std::move(callback).Run(
+ was_subscribed
+ ? blink::mojom::PushUnregistrationStatus::SUCCESS_UNREGISTERED
+ : blink::mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED);
+
+ if (PushMessagingAppIdentifier::UseInstanceID(app_id)) {
+ GetInstanceIDDriver()->GetInstanceID(app_id)->DeleteID(
+ base::BindOnce(&PushMessagingServiceImpl::DidDeleteID,
+ weak_factory_.GetWeakPtr(), app_id, was_subscribed));
+
+ } else {
+ auto unregister_callback =
+ base::BindOnce(&PushMessagingServiceImpl::DidUnregister,
+ weak_factory_.GetWeakPtr(), was_subscribed);
+#if defined(OS_ANDROID)
+ // On Android the backend is different, and requires the original sender_id.
+ // DidGetSenderIdUnexpectedUnsubscribe and
+ // DidDeleteServiceWorkerRegistration sometimes call us with an empty one.
+ if (sender_id.empty()) {
+ std::move(unregister_callback).Run(gcm::GCMClient::INVALID_PARAMETER);
+ } else {
+ GetGCMDriver()->UnregisterWithSenderId(
+ app_id, push_messaging::NormalizeSenderInfo(sender_id),
+ std::move(unregister_callback));
+ }
+#else
+ GetGCMDriver()->Unregister(app_id, std::move(unregister_callback));
+#endif
+ }
+}
+
+void PushMessagingServiceImpl::DidUnregister(bool was_subscribed,
+ gcm::GCMClient::Result result) {
+ RecordUnsubscribeGCMResult(result);
+ DidUnsubscribe(std::string() /* app_id_when_instance_id */, was_subscribed);
+}
+
+void PushMessagingServiceImpl::DidDeleteID(const std::string& app_id,
+ bool was_subscribed,
+ InstanceID::Result result) {
+ RecordUnsubscribeIIDResult(result);
+ // DidUnsubscribe must be run asynchronously when passing a non-empty
+ // |app_id_when_instance_id|, since it calls
+ // InstanceIDDriver::RemoveInstanceID which deletes the InstanceID itself.
+ // Calling that immediately would cause a use-after-free in our caller.
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&PushMessagingServiceImpl::DidUnsubscribe,
+ weak_factory_.GetWeakPtr(), app_id, was_subscribed));
+}
+
+void PushMessagingServiceImpl::DidUnsubscribe(
+ const std::string& app_id_when_instance_id,
+ bool was_subscribed) {
+ if (!app_id_when_instance_id.empty())
+ GetInstanceIDDriver()->RemoveInstanceID(app_id_when_instance_id);
+
+ if (was_subscribed)
+ DecreasePushSubscriptionCount(1, false /* was_pending */);
+
+ if (!unsubscribe_callback_for_testing_.is_null())
+ std::move(unsubscribe_callback_for_testing_).Run();
+}
+
+void PushMessagingServiceImpl::SetUnsubscribeCallbackForTesting(
+ base::OnceClosure callback) {
+ unsubscribe_callback_for_testing_ = std::move(callback);
+}
+
+// DidDeleteServiceWorkerRegistration methods ----------------------------------
+
+void PushMessagingServiceImpl::DidDeleteServiceWorkerRegistration(
+ const GURL& origin,
+ int64_t service_worker_registration_id) {
+ const PushMessagingAppIdentifier& app_identifier =
+ PushMessagingAppIdentifier::FindByServiceWorker(
+ profile_, origin, service_worker_registration_id);
+ if (app_identifier.is_null()) {
+ if (!service_worker_unregistered_callback_for_testing_.is_null())
+ service_worker_unregistered_callback_for_testing_.Run();
+ return;
+ }
+ // Note this will not fully unsubscribe pre-InstanceID subscriptions on
+ // Android from GCM, as that requires a sender_id. (Ideally we'd fetch it
+ // from the SWDB in some "before_unregistered" SWObserver event.)
+ UnsubscribeInternal(
+ blink::mojom::PushUnregistrationReason::SERVICE_WORKER_UNREGISTERED,
+ origin, service_worker_registration_id, app_identifier.app_id(),
+ std::string() /* sender_id */,
+ base::BindOnce(&UnregisterCallbackToClosure,
+ service_worker_unregistered_callback_for_testing_.is_null()
+ ? base::DoNothing()
+ : service_worker_unregistered_callback_for_testing_));
+}
+
+void PushMessagingServiceImpl::SetServiceWorkerUnregisteredCallbackForTesting(
+ base::RepeatingClosure callback) {
+ service_worker_unregistered_callback_for_testing_ = std::move(callback);
+}
+
+// DidDeleteServiceWorkerDatabase methods --------------------------------------
+
+void PushMessagingServiceImpl::DidDeleteServiceWorkerDatabase() {
+ std::vector<PushMessagingAppIdentifier> app_identifiers =
+ PushMessagingAppIdentifier::GetAll(profile_);
+
+ base::RepeatingClosure completed_closure = base::BarrierClosure(
+ app_identifiers.size(),
+ service_worker_database_wiped_callback_for_testing_.is_null()
+ ? base::DoNothing()
+ : service_worker_database_wiped_callback_for_testing_);
+
+ for (const PushMessagingAppIdentifier& app_identifier : app_identifiers) {
+ // Note this will not fully unsubscribe pre-InstanceID subscriptions on
+ // Android from GCM, as that requires a sender_id. We can't fetch those from
+ // the Service Worker database anymore as it's been deleted.
+ UnsubscribeInternal(
+ blink::mojom::PushUnregistrationReason::SERVICE_WORKER_DATABASE_WIPED,
+ app_identifier.origin(),
+ app_identifier.service_worker_registration_id(),
+ app_identifier.app_id(), std::string() /* sender_id */,
+ base::BindOnce(&UnregisterCallbackToClosure, completed_closure));
+ }
+}
+
+void PushMessagingServiceImpl::SetServiceWorkerDatabaseWipedCallbackForTesting(
+ base::RepeatingClosure callback) {
+ service_worker_database_wiped_callback_for_testing_ = std::move(callback);
+}
+
+// OnContentSettingChanged methods ---------------------------------------------
+
+void PushMessagingServiceImpl::OnContentSettingChanged(
+ const ContentSettingsPattern& primary_pattern,
+ const ContentSettingsPattern& secondary_pattern,
+ ContentSettingsTypeSet content_type_set) {
+ DCHECK(primary_pattern.IsValid());
+ if (!content_type_set.Contains(ContentSettingsType::NOTIFICATIONS))
+ return;
+
+ std::vector<PushMessagingAppIdentifier> all_app_identifiers =
+ PushMessagingAppIdentifier::GetAll(profile_);
+
+ base::RepeatingClosure barrier_closure = base::BarrierClosure(
+ all_app_identifiers.size(),
+ content_setting_changed_callback_for_testing_.is_null()
+ ? base::DoNothing()
+ : content_setting_changed_callback_for_testing_);
+
+ for (const PushMessagingAppIdentifier& app_identifier : all_app_identifiers) {
+ if (!primary_pattern.Matches(app_identifier.origin())) {
+ barrier_closure.Run();
+ continue;
+ }
+
+ if (IsPermissionSet(app_identifier.origin())) {
+ barrier_closure.Run();
+ continue;
+ }
+
+ UnexpectedChange(app_identifier,
+ blink::mojom::PushUnregistrationReason::PERMISSION_REVOKED,
+ barrier_closure);
+ }
+}
+
+void PushMessagingServiceImpl::UnexpectedUnsubscribe(
+ const PushMessagingAppIdentifier& app_identifier,
+ blink::mojom::PushUnregistrationReason reason,
+ UnregisterCallback unregister_callback) {
+ // When `pushsubscriptionchange` is supported by default, get |sender_id| from
+ // GetPushSubscriptionFromAppIdentifier callback and do not get the info from
+ // IO twice
+ bool need_sender_id = false;
+#if defined(OS_ANDROID)
+ need_sender_id =
+ !PushMessagingAppIdentifier::UseInstanceID(app_identifier.app_id());
+#endif
+ if (need_sender_id) {
+ GetSenderId(
+ profile_, app_identifier.origin(),
+ app_identifier.service_worker_registration_id(),
+ base::BindOnce(
+ &PushMessagingServiceImpl::DidGetSenderIdUnexpectedUnsubscribe,
+ weak_factory_.GetWeakPtr(), app_identifier, reason,
+ std::move(unregister_callback)));
+ } else {
+ UnsubscribeInternal(reason, app_identifier.origin(),
+ app_identifier.service_worker_registration_id(),
+ app_identifier.app_id(),
+ std::string() /* sender_id */,
+ std::move(unregister_callback));
+ }
+}
+
+void PushMessagingServiceImpl::GetPushSubscriptionFromAppIdentifier(
+ const PushMessagingAppIdentifier& app_identifier,
+ base::OnceCallback<void(blink::mojom::PushSubscriptionPtr)>
+ subscription_cb) {
+ GetSWData(profile_, app_identifier.origin(),
+ app_identifier.service_worker_registration_id(),
+ base::BindOnce(&PushMessagingServiceImpl::DidGetSWData,
+ weak_factory_.GetWeakPtr(), app_identifier,
+ std::move(subscription_cb)));
+}
+
+void PushMessagingServiceImpl::DidGetSWData(
+ const PushMessagingAppIdentifier& app_identifier,
+ base::OnceCallback<void(blink::mojom::PushSubscriptionPtr)> subscription_cb,
+ const std::string& sender_id,
+ const std::string& subscription_id) {
+ // SW Database was corrupted, return immediately
+ if (sender_id.empty() || subscription_id.empty()) {
+ std::move(subscription_cb).Run(nullptr /* subscription */);
+ return;
+ }
+ GetSubscriptionInfo(
+ app_identifier.origin(), app_identifier.service_worker_registration_id(),
+ sender_id, subscription_id,
+ base::BindOnce(
+ &PushMessagingServiceImpl::GetPushSubscriptionFromAppIdentifierEnd,
+ weak_factory_.GetWeakPtr(), std::move(subscription_cb), sender_id));
+}
+
+void PushMessagingServiceImpl::GetPushSubscriptionFromAppIdentifierEnd(
+ base::OnceCallback<void(blink::mojom::PushSubscriptionPtr)> callback,
+ const std::string& sender_id,
+ bool is_valid,
+ const GURL& endpoint,
+ const absl::optional<base::Time>& expiration_time,
+ const std::vector<uint8_t>& p256dh,
+ const std::vector<uint8_t>& auth) {
+ if (!is_valid) {
+ // TODO(viviy): Log error in UMA
+ std::move(callback).Run(nullptr /* subscription */);
+ return;
+ }
+
+ std::move(callback).Run(blink::mojom::PushSubscription::New(
+ endpoint, expiration_time, push_messaging::MakeOptions(sender_id), p256dh,
+ auth));
+}
+
+void PushMessagingServiceImpl::FirePushSubscriptionChange(
+ const PushMessagingAppIdentifier& app_identifier,
+ base::OnceClosure completed_closure,
+ blink::mojom::PushSubscriptionPtr new_subscription,
+ blink::mojom::PushSubscriptionPtr old_subscription) {
+ // Ensure |completed_closure| is run after this function
+ base::ScopedClosureRunner scoped_closure(std::move(completed_closure));
+
+ if (!base::FeatureList::IsEnabled(features::kPushSubscriptionChangeEvent))
+ return;
+
+ if (app_identifier.is_null()) {
+ FirePushSubscriptionChangeCallback(
+ app_identifier, blink::mojom::PushEventStatus::UNKNOWN_APP_ID);
+ return;
+ }
+
+ profile_->FirePushSubscriptionChangeEvent(
+ app_identifier.origin(), app_identifier.service_worker_registration_id(),
+ std::move(new_subscription), std::move(old_subscription),
+ base::BindOnce(
+ &PushMessagingServiceImpl::FirePushSubscriptionChangeCallback,
+ weak_factory_.GetWeakPtr(), app_identifier));
+}
+
+void PushMessagingServiceImpl::FirePushSubscriptionChangeCallback(
+ const PushMessagingAppIdentifier& app_identifier,
+ blink::mojom::PushEventStatus status) {
+ // Log Data in UMA
+ RecordPushSubcriptionChangeStatus(status);
+}
+
+void PushMessagingServiceImpl::DidGetSenderIdUnexpectedUnsubscribe(
+ const PushMessagingAppIdentifier& app_identifier,
+ blink::mojom::PushUnregistrationReason reason,
+ UnregisterCallback callback,
+ const std::string& sender_id) {
+ // 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 for legacy GCM registrations on
+ // Android, Unsubscribe will just delete the app identifier to block future
+ // messages.
+ // TODO(johnme): Auto-unregister before SW DB is cleared (crbug.com/402458).
+ UnsubscribeInternal(reason, app_identifier.origin(),
+ app_identifier.service_worker_registration_id(),
+ app_identifier.app_id(), sender_id, std::move(callback));
+}
+
+void PushMessagingServiceImpl::SetContentSettingChangedCallbackForTesting(
+ base::RepeatingClosure callback) {
+ content_setting_changed_callback_for_testing_ = std::move(callback);
+}
+
+// KeyedService methods -------------------------------------------------------
+
+void PushMessagingServiceImpl::Shutdown() {
+ GetGCMDriver()->RemoveAppHandler(kPushMessagingAppIdentifierPrefix);
+ HostContentSettingsMapFactory::GetForProfile(profile_)->RemoveObserver(this);
+}
+
+// content::NotificationObserver methods ---------------------------------------
+
+void PushMessagingServiceImpl::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK_EQ(chrome::NOTIFICATION_APP_TERMINATING, type);
+ shutdown_started_ = true;
+#if BUILDFLAG(ENABLE_BACKGROUND_MODE)
+ in_flight_keep_alive_.reset();
+ in_flight_profile_keep_alive_.reset();
+#endif // BUILDFLAG(ENABLE_BACKGROUND_MODE)
+}
+
+// OnSubscriptionInvalidation methods ------------------------------------------
+
+void PushMessagingServiceImpl::OnSubscriptionInvalidation(
+ const std::string& app_id) {
+ DCHECK(base::FeatureList::IsEnabled(features::kPushSubscriptionChangeEvent))
+ << "It is not allowed to call this method when "
+ "features::kPushSubscriptionChangeEvent is disabled.";
+ PushMessagingAppIdentifier old_app_identifier =
+ PushMessagingAppIdentifier::FindByAppId(profile_, app_id);
+ if (old_app_identifier.is_null())
+ return;
+
+ GetSenderId(profile_, old_app_identifier.origin(),
+ old_app_identifier.service_worker_registration_id(),
+ base::BindOnce(&PushMessagingServiceImpl::GetOldSubscription,
+ weak_factory_.GetWeakPtr(), old_app_identifier));
+}
+
+void PushMessagingServiceImpl::GetOldSubscription(
+ PushMessagingAppIdentifier old_app_identifier,
+ const std::string& sender_id) {
+ GetPushSubscriptionFromAppIdentifier(
+ old_app_identifier,
+ base::BindOnce(&PushMessagingServiceImpl::StartRefresh,
+ weak_factory_.GetWeakPtr(), old_app_identifier,
+ sender_id));
+}
+
+void PushMessagingServiceImpl::StartRefresh(
+ PushMessagingAppIdentifier old_app_identifier,
+ const std::string& sender_id,
+ blink::mojom::PushSubscriptionPtr old_subscription) {
+ // Generate a new app_identifier with the same information, but a different
+ // app_id. Expiration time will be overwritten by DoSubscribe, if the flag
+ // features::kPushSubscriptionWithExpiration time is enabled
+ PushMessagingAppIdentifier new_app_identifier =
+ PushMessagingAppIdentifier::Generate(
+ old_app_identifier.origin(),
+ old_app_identifier.service_worker_registration_id(),
+ absl::nullopt /* expiration_time */);
+
+ refresher_.Refresh(old_app_identifier, new_app_identifier.app_id(),
+ sender_id);
+
+ UpdateSubscription(
+ new_app_identifier, push_messaging::MakeOptions(sender_id),
+ base::BindOnce(&PushMessagingServiceImpl::DidUpdateSubscription,
+ weak_factory_.GetWeakPtr(), new_app_identifier.app_id(),
+ old_app_identifier.app_id(), std::move(old_subscription),
+ sender_id));
+}
+
+void PushMessagingServiceImpl::UpdateSubscription(
+ PushMessagingAppIdentifier app_identifier,
+ blink::mojom::PushSubscriptionOptionsPtr options,
+ RegisterCallback callback) {
+ // After getting a new GCM registration, update the |subscription_id| in SW
+ // database before running the callback
+ auto register_callback = base::BindOnce(
+ [](RegisterCallback cb, Profile* profile, PushMessagingAppIdentifier ai,
+ const std::string& registration_id, const GURL& endpoint,
+ const absl::optional<base::Time>& expiration_time,
+ const std::vector<uint8_t>& p256dh, const std::vector<uint8_t>& auth,
+ blink::mojom::PushRegistrationStatus status) {
+ base::OnceClosure closure =
+ base::BindOnce(std::move(cb), registration_id, endpoint,
+ expiration_time, p256dh, auth, status);
+ base::ScopedClosureRunner closure_runner(std::move(closure));
+ if (status ==
+ blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE) {
+ UpdatePushSubscriptionId(profile, ai.origin(),
+ ai.service_worker_registration_id(),
+ registration_id, closure_runner.Release());
+ }
+ },
+ std::move(callback), profile_, app_identifier);
+ // Subscribe using the new subscription information, this will overwrite
+ // the expiration time of |app_identifier|
+ DoSubscribe(app_identifier, std::move(options), std::move(register_callback),
+ -1 /* render_process_id */, -1 /* render_frame_id */,
+ CONTENT_SETTING_ALLOW);
+}
+
+void PushMessagingServiceImpl::DidUpdateSubscription(
+ const std::string& new_app_id,
+ const std::string& old_app_id,
+ blink::mojom::PushSubscriptionPtr old_subscription,
+ const std::string& sender_id,
+ const std::string& registration_id,
+ const GURL& endpoint,
+ const absl::optional<base::Time>& expiration_time,
+ const std::vector<uint8_t>& p256dh,
+ const std::vector<uint8_t>& auth,
+ blink::mojom::PushRegistrationStatus status) {
+ // TODO(crbug.com/1122545): Currently, if |status| is unsuccessful, the old
+ // subscription remains in SW database and preferences and the refresh is
+ // aborted. Instead, one should abort the refresh and retry to refresh
+ // periodically.
+ if (status !=
+ blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE) {
+ return;
+ }
+
+ // Old subscription is now replaced locally by the new subscription
+ refresher_.OnSubscriptionUpdated(new_app_id);
+
+ PushMessagingAppIdentifier new_app_identifier =
+ PushMessagingAppIdentifier::FindByAppId(profile_, new_app_id);
+
+ // Callback for testing
+ base::OnceClosure callback =
+ (invalidation_callback_for_testing_)
+ ? std::move(invalidation_callback_for_testing_)
+ : base::DoNothing();
+
+ FirePushSubscriptionChange(
+ new_app_identifier, std::move(callback),
+ blink::mojom::PushSubscription::New(
+ endpoint, expiration_time, push_messaging::MakeOptions(sender_id),
+ p256dh, auth),
+ std::move(old_subscription));
+}
+
+// PushMessagingRefresher::Observer methods ------------------------------------
+
+void PushMessagingServiceImpl::OnOldSubscriptionExpired(
+ const std::string& app_id,
+ const std::string& sender_id) {
+ // Unsubscribe without clearing SW database, since values of the new
+ // subscription are already saved there.
+ // After unsubscribing, the refresher will get notified.
+ UnsubscribeInternal(
+ blink::mojom::PushUnregistrationReason::REFRESH_FINISHED,
+ GURL::EmptyGURL() /* origin */, -1 /* service_worker_registration_id */,
+ app_id, sender_id,
+ base::BindOnce(&UnregisterCallbackToClosure,
+ base::BindOnce(&PushMessagingRefresher::OnUnsubscribed,
+ refresher_.GetWeakPtr(), app_id)));
+}
+
+void PushMessagingServiceImpl::OnRefreshFinished(
+ const PushMessagingAppIdentifier& app_identifier) {
+ // TODO(viviy): Log data in UMA
+}
+
+void PushMessagingServiceImpl::SetInvalidationCallbackForTesting(
+ base::OnceClosure callback) {
+ invalidation_callback_for_testing_ = std::move(callback);
+}
+
+// Helper methods --------------------------------------------------------------
+
+void PushMessagingServiceImpl::SetRemoveExpiredSubscriptionsCallbackForTesting(
+ base::OnceClosure closure) {
+ remove_expired_subscriptions_callback_for_testing_ = std::move(closure);
+}
+
+// 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,
+ bool user_visible) {
+ return GetPermissionStatus(origin, user_visible) ==
+ blink::mojom::PermissionStatus::GRANTED;
+}
+
+void PushMessagingServiceImpl::GetEncryptionInfoForAppId(
+ const std::string& app_id,
+ const std::string& sender_id,
+ gcm::GCMEncryptionProvider::EncryptionInfoCallback callback) {
+ if (PushMessagingAppIdentifier::UseInstanceID(app_id)) {
+ GetInstanceIDDriver()->GetInstanceID(app_id)->GetEncryptionInfo(
+ push_messaging::NormalizeSenderInfo(sender_id), std::move(callback));
+ } else {
+ GetGCMDriver()->GetEncryptionInfo(app_id, std::move(callback));
+ }
+}
+
+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();
+}
+
+instance_id::InstanceIDDriver* PushMessagingServiceImpl::GetInstanceIDDriver()
+ const {
+ instance_id::InstanceIDProfileService* instance_id_profile_service =
+ instance_id::InstanceIDProfileServiceFactory::GetForProfile(profile_);
+ CHECK(instance_id_profile_service);
+ CHECK(instance_id_profile_service->driver());
+ return instance_id_profile_service->driver();
+}
+
+content::DevToolsBackgroundServicesContext*
+PushMessagingServiceImpl::GetDevToolsContext(const GURL& origin) const {
+ auto* storage_partition = profile_->GetStoragePartitionForUrl(origin);
+ if (!storage_partition)
+ return nullptr;
+
+ auto* devtools_context =
+ storage_partition->GetDevToolsBackgroundServicesContext();
+
+ if (!devtools_context->IsRecording(
+ content::DevToolsBackgroundService::kPushMessaging)) {
+ return nullptr;
+ }
+
+ return devtools_context;
+}