|  | // Copyright 2014 The Chromium Authors | 
|  | // 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/command_line.h" | 
|  | #include "base/feature_list.h" | 
|  | #include "base/functional/bind.h" | 
|  | #include "base/functional/callback_helpers.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/task/single_thread_task_runner.h" | 
|  | #include "base/time/time.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/gcm/gcm_profile_service_factory.h" | 
|  | #include "chrome/browser/gcm/instance_id/instance_id_profile_service_factory.h" | 
|  | #include "chrome/browser/lifetime/termination_notification.h" | 
|  | #include "chrome/browser/permissions/permission_revocation_request.h" | 
|  | #include "chrome/browser/profiles/keep_alive/profile_keep_alive_types.h" | 
|  | #include "chrome/browser/profiles/keep_alive/scoped_profile_keep_alive.h" | 
|  | #include "chrome/browser/profiles/profile.h" | 
|  | #include "chrome/browser/push_messaging/push_messaging_app_identifier.h" | 
|  | #include "chrome/browser/push_messaging/push_messaging_service_factory.h" | 
|  | #include "chrome/browser/push_messaging/push_messaging_unsubscribed_entry.h" | 
|  | #include "chrome/browser/ui/chrome_pages.h" | 
|  | #include "chrome/common/buildflags.h" | 
|  | #include "chrome/common/channel_info.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_uma_util.h" | 
|  | #include "components/pref_registry/pref_registry_syncable.h" | 
|  | #include "components/prefs/pref_registry_simple.h" | 
|  | #include "components/prefs/pref_service.h" | 
|  | #include "components/push_messaging/app_identifier.h" | 
|  | #include "components/push_messaging/push_messaging_constants.h" | 
|  | #include "components/push_messaging/push_messaging_features.h" | 
|  | #include "components/push_messaging/push_messaging_utils.h" | 
|  | #include "content/public/browser/browser_context.h" | 
|  | #include "content/public/browser/child_process_host.h" | 
|  | #include "content/public/browser/devtools_background_services_context.h" | 
|  | #include "content/public/browser/permission_controller.h" | 
|  | #include "content/public/browser/permission_descriptor_util.h" | 
|  | #include "content/public/browser/permission_result.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/render_process_host.h" | 
|  | #include "content/public/browser/service_worker_context.h" | 
|  | #include "content/public/browser/storage_partition.h" | 
|  | #include "content/public/common/content_features.h" | 
|  | #include "content/public/common/content_switches.h" | 
|  | #include "extensions/common/constants.h" | 
|  | #include "third_party/blink/public/common/permissions/permission_utils.h" | 
|  | #include "third_party/blink/public/common/storage_key/storage_key.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" | 
|  | #include "url/origin.h" | 
|  |  | 
|  | #if BUILDFLAG(ENABLE_BACKGROUND_MODE) | 
|  | #include "components/keep_alive_registry/keep_alive_types.h" | 
|  | #include "components/keep_alive_registry/scoped_keep_alive.h" | 
|  | #endif | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | #include "base/android/jni_android.h" | 
|  | #include "base/android/jni_string.h" | 
|  | #include "chrome/android/chrome_jni_headers/PushMessagingServiceBridge_jni.h" | 
|  | #include "chrome/android/chrome_jni_headers/PushMessagingServiceObserver_jni.h" | 
|  | #include "chrome/browser/android/shortcut_helper.h" | 
|  | #include "chrome/browser/notifications/notification_platform_bridge.h" | 
|  | #include "chrome/browser/profiles/profile_manager.h" | 
|  | #include "components/permissions/android/android_permission_util.h" | 
|  | #endif | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | using base::android::ConvertJavaStringToUTF8; | 
|  | using base::android::JavaParamRef; | 
|  | #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."; | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | // The serialized base::Time used for Notifications permission revocation grace | 
|  | // period checks. This is usually the time at which the first push message was | 
|  | // received without app-level Notifications permission. An empty | 
|  | // (default-constructed) base::Time if there is no known time without app-level | 
|  | // Notifications permission. | 
|  | const char kNotificationsPermissionRevocationGracePeriodDate[] = | 
|  | "notifications_permission_revocation_grace_period"; | 
|  | #endif | 
|  |  | 
|  | void RecordDeliveryStatus(blink::mojom::PushEventStatus status) { | 
|  | UMA_HISTOGRAM_ENUMERATION("PushMessaging.DeliveryStatus", status); | 
|  | } | 
|  |  | 
|  | void RecordUnsubscribeReason(blink::mojom::PushUnregistrationReason reason) { | 
|  | UMA_HISTOGRAM_ENUMERATION("PushMessaging.UnregistrationReason", reason); | 
|  | } | 
|  |  | 
|  | void UnregisterCallbackToClosure( | 
|  | base::OnceClosure closure, | 
|  | blink::mojom::PushUnregistrationStatus status) { | 
|  | DCHECK(closure); | 
|  | std::move(closure).Run(); | 
|  | } | 
|  |  | 
|  | void LogMessageReceivedEventToDevTools( | 
|  | content::DevToolsBackgroundServicesContext* devtools_context, | 
|  | const push_messaging::AppIdentifier& 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; | 
|  |  | 
|  | url::Origin origin = url::Origin::Create(app_identifier.origin()); | 
|  | devtools_context->LogBackgroundServiceEvent( | 
|  | app_identifier.service_worker_registration_id(), | 
|  | blink::StorageKey::CreateFirstParty(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; | 
|  |  | 
|  | GURL CreateEndpointFromChromeChannel(const std::string& subscription_id) { | 
|  | return push_messaging::CreateEndpoint(chrome::GetChannel(), subscription_id); | 
|  | } | 
|  |  | 
|  | }  // 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::SingleThreadTaskRunner::GetCurrentDefault()->PostTask( | 
|  | FROM_HERE, barrier_closure); | 
|  | continue; | 
|  | } | 
|  | content::BrowserThread::PostBestEffortTask( | 
|  | FROM_HERE, base::SingleThreadTaskRunner::GetCurrentDefault(), | 
|  | base::BindOnce( | 
|  | &PushMessagingServiceImpl::UnexpectedChange, | 
|  | weak_factory_.GetWeakPtr(), identifier, | 
|  | blink::mojom::PushUnregistrationReason::SUBSCRIPTION_EXPIRED, | 
|  | barrier_closure)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::UnexpectedChange( | 
|  | push_messaging::AppIdentifier 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::kPushSubscriptionChangeEventOnInvalidation)) { | 
|  | // Find old subscription and fire a `pushsubscriptionchange` event | 
|  | GetPushSubscriptionFromAppIdentifier( | 
|  | identifier, | 
|  | base::BindOnce(&PushMessagingServiceImpl:: | 
|  | FirePushSubscriptionChangeForAppIdentifier, | 
|  | weak_factory_.GetWeakPtr(), identifier, | 
|  | std::move(unsubscribe_closure), | 
|  | nullptr /* new_subscription */)); | 
|  | } else { | 
|  | std::move(unsubscribe_closure).Run(); | 
|  | } | 
|  | } | 
|  |  | 
|  | PushMessagingServiceImpl::PushMessagingServiceImpl( | 
|  | Profile* profile, | 
|  | scoped_refptr<safe_browsing::SafeBrowsingDatabaseManager> database_manager) | 
|  | : profile_(profile), | 
|  | push_subscription_count_(0), | 
|  | pending_push_subscription_count_(0), | 
|  | notification_manager_(profile), | 
|  | database_manager_(database_manager) { | 
|  | DCHECK(profile); | 
|  | HostContentSettingsMapFactory::GetForProfile(profile_)->AddObserver(this); | 
|  |  | 
|  | on_app_terminating_subscription_ = | 
|  | browser_shutdown::AddAppTerminatingCallback(base::BindOnce( | 
|  | &PushMessagingServiceImpl::OnAppTerminating, base::Unretained(this))); | 
|  | 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(push_messaging::kAppIdentifierPrefix, 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(push_messaging::kAppIdentifierPrefix); | 
|  | } | 
|  |  | 
|  | bool PushMessagingServiceImpl::CanHandle(const std::string& app_id) const { | 
|  | return base::StartsWith(app_id, push_messaging::kAppIdentifierPrefix, | 
|  | 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 (!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); | 
|  |  | 
|  | push_messaging::AppIdentifier app_identifier = | 
|  | PushMessagingAppIdentifier::FindByAppId(profile_, app_id); | 
|  | // Drop message and unregister if app_id was unknown (maybe recently deleted). | 
|  | if (app_identifier.is_null()) { | 
|  | std::optional<push_messaging::AppIdentifier> refresh_identifier = | 
|  | refresher_.FindActiveAppIdentifier(app_id); | 
|  | if (!refresh_identifier) { | 
|  | DeliverMessageCallback(app_id, GURL(), | 
|  | /*service_worker_registration_id=*/-1, message, | 
|  | /*did_enqueue_message=*/false, | 
|  | 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()); | 
|  |  | 
|  | bool user_visible = | 
|  | !base::Contains(origins_requesting_user_visible_requirement_bypass, | 
|  | app_identifier.origin()); | 
|  | if (IsPermissionSet(app_identifier.origin(), user_visible)) { | 
|  | messages_pending_permission_check_.emplace(app_id, message); | 
|  | // Start abusive and disruptive origin verifications only if no other | 
|  | // respective verification is in progress. | 
|  | if (!origin_revocation_request_) | 
|  | CheckOriginAndDispatchNextMessage(); | 
|  | } else { | 
|  | // Drop message and unregister if origin has lost push permission. | 
|  | DeliverMessageCallback(app_id, app_identifier.origin(), | 
|  | app_identifier.service_worker_registration_id(), | 
|  | message, /*did_enqueue_message=*/false, | 
|  | blink::mojom::PushEventStatus::PERMISSION_DENIED); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::CheckOriginAndDispatchNextMessage() { | 
|  | if (messages_pending_permission_check_.empty()) | 
|  | return; | 
|  |  | 
|  | PendingMessage message = | 
|  | std::move(messages_pending_permission_check_.front()); | 
|  | messages_pending_permission_check_.pop(); | 
|  |  | 
|  | push_messaging::AppIdentifier app_identifier = | 
|  | PushMessagingAppIdentifier::FindByAppId(profile_, message.app_id); | 
|  |  | 
|  | if (app_identifier.is_null()) { | 
|  | CheckOriginAndDispatchNextMessage(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | DCHECK(!origin_revocation_request_) | 
|  | << "Create one Origin Revocation instance per request."; | 
|  | origin_revocation_request_ = std::make_unique<PermissionRevocationRequest>( | 
|  | profile_, app_identifier.origin(), | 
|  | base::BindOnce(&PushMessagingServiceImpl::OnCheckedOrigin, | 
|  | weak_factory_.GetWeakPtr(), std::move(message))); | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::OnCheckedOrigin( | 
|  | PendingMessage message, | 
|  | PermissionRevocationRequest::Outcome outcome) { | 
|  | origin_revocation_request_.reset(); | 
|  |  | 
|  | push_messaging::AppIdentifier app_identifier = | 
|  | PushMessagingAppIdentifier::FindByAppId(profile_, message.app_id); | 
|  |  | 
|  | if (app_identifier.is_null()) { | 
|  | CheckOriginAndDispatchNextMessage(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const GURL& origin = app_identifier.origin(); | 
|  | int64_t service_worker_registration_id = | 
|  | app_identifier.service_worker_registration_id(); | 
|  |  | 
|  | bool user_visible = !base::Contains( | 
|  | origins_requesting_user_visible_requirement_bypass, origin); | 
|  |  | 
|  | // It is possible that Notifications permission has been revoked by a user | 
|  | // during abusive origin verification. | 
|  | if (outcome == PermissionRevocationRequest::Outcome::PERMISSION_NOT_REVOKED && | 
|  | IsPermissionSet(origin, user_visible)) { | 
|  | 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 { | 
|  | blink::mojom::PushEventStatus status; | 
|  |  | 
|  | switch (outcome) { | 
|  | case PermissionRevocationRequest::Outcome::PERMISSION_NOT_REVOKED: | 
|  | status = blink::mojom::PushEventStatus::PERMISSION_DENIED; | 
|  | break; | 
|  | case PermissionRevocationRequest::Outcome:: | 
|  | PERMISSION_REVOKED_DUE_TO_ABUSE: | 
|  | status = blink::mojom::PushEventStatus::PERMISSION_REVOKED_ABUSIVE; | 
|  | break; | 
|  | case PermissionRevocationRequest::Outcome:: | 
|  | PERMISSION_REVOKED_DUE_TO_DISRUPTIVE_BEHAVIOR: | 
|  | status = blink::mojom::PushEventStatus::PERMISSION_REVOKED_DISRUPTIVE; | 
|  | break; | 
|  | default: | 
|  | NOTREACHED(); | 
|  | } | 
|  |  | 
|  | // Drop message and unregister if origin has lost push permission. | 
|  | DeliverMessageCallback(message.app_id, origin, | 
|  | service_worker_registration_id, message.message, | 
|  | /* did_enqueue_message */ false, status); | 
|  | } | 
|  |  | 
|  | // Verify the next message in the queue. | 
|  | CheckOriginAndDispatchNextMessage(); | 
|  | } | 
|  |  | 
|  | 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, /*did_enqueue_message=*/true); | 
|  |  | 
|  | bool user_visible = !base::Contains( | 
|  | origins_requesting_user_visible_requirement_bypass, origin); | 
|  |  | 
|  | // It is possible that Notification permissions have been revoked by a user | 
|  | // while handling previous messages for |origin|. | 
|  | if (!IsPermissionSet(origin, user_visible)) { | 
|  | 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. | 
|  | std::optional<std::string> payload; | 
|  | if (message.decrypted) | 
|  | payload = message.raw_data; | 
|  |  | 
|  | // 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; | 
|  | } | 
|  | if (database_manager_) { | 
|  | database_manager_->CheckUrlForHighConfidenceAllowlist( | 
|  | origin, | 
|  | base::BindOnce( | 
|  | &PushMessagingServiceImpl::DidCheckHighConfidenceAllowlist, | 
|  | weak_factory_.GetWeakPtr(), origin, service_worker_registration_id, | 
|  | message.message_id, payload, std::move(deliver_message_callback))); | 
|  | } else { | 
|  | // Dispatch the message to the appropriate Service Worker. | 
|  | profile_->DeliverPushMessage(origin, service_worker_registration_id, | 
|  | message.message_id, payload, | 
|  | /* record_network_requests= */ false, | 
|  | std::move(deliver_message_callback)); | 
|  | } | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::DidCheckHighConfidenceAllowlist( | 
|  | const GURL& origin, | 
|  | int64_t service_worker_registration_id, | 
|  | const std::string& message_id, | 
|  | std::optional<std::string> payload, | 
|  | base::OnceCallback<void(blink::mojom::PushEventStatus)> callback, | 
|  | bool allowlisted, | 
|  | std::optional<safe_browsing::SafeBrowsingDatabaseManager:: | 
|  | HighConfidenceAllowlistCheckLoggingDetails> | 
|  | logging_details) { | 
|  | profile_->DeliverPushMessage( | 
|  | origin, service_worker_registration_id, message_id, payload, | 
|  | /*record_network_requests=*/!allowlisted, std::move(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)) { | 
|  | // Defaults to true since that is the more restrictive option. | 
|  | bool user_visible_only = | 
|  | base::Contains(origins_requesting_user_visible_requirement_bypass, | 
|  | requesting_origin); | 
|  | notification_manager_.EnforceUserVisibleOnlyRequirements( | 
|  | requesting_origin, service_worker_registration_id, | 
|  | std::move(message_handled_callback), user_visible_only); | 
|  | 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::NO_APP_LEVEL_PERMISSION_IGNORE: | 
|  | // Do nothing, ignore push messages during the grace period. | 
|  | break; | 
|  | case blink::mojom::PushEventStatus::NO_APP_LEVEL_PERMISSION_UNSUBSCRIBE: | 
|  | unsubscribe_reason = | 
|  | blink::mojom::PushUnregistrationReason::NO_APP_LEVEL_PERMISSION; | 
|  | 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; | 
|  | case blink::mojom::PushEventStatus::PERMISSION_REVOKED_DISRUPTIVE: | 
|  | unsubscribe_reason = | 
|  | blink::mojom::PushUnregistrationReason::PERMISSION_REVOKED_DISRUPTIVE; | 
|  | 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) { | 
|  | push_messaging::AppIdentifier app_identifier = | 
|  | PushMessagingAppIdentifier::FindByAppId(profile_, app_id); | 
|  | UnsubscribeInternal( | 
|  | unsubscribe_reason, | 
|  | app_identifier.is_null() ? GURL() : 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; | 
|  |  | 
|  | url::Origin origin = url::Origin::Create(app_identifier.origin()); | 
|  | if (auto* devtools_context = GetDevToolsContext(app_identifier.origin())) { | 
|  | std::stringstream ss; | 
|  | ss << unsubscribe_reason; | 
|  | devtools_context->LogBackgroundServiceEvent( | 
|  | app_identifier.service_worker_registration_id(), | 
|  | blink::StorageKey::CreateFirstParty(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()); | 
|  |  | 
|  | 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 BUILDFLAG(IS_ANDROID) | 
|  | chrome::android::Java_PushMessagingServiceObserver_onMessageHandled( | 
|  | base::android::AttachCurrentThread()); | 
|  | #endif | 
|  |  | 
|  | push_messaging::AppIdentifier app_identifier = | 
|  | PushMessagingAppIdentifier::FindByAppId(profile_, app_id); | 
|  |  | 
|  | if (app_identifier.is_null() || !did_show_generic_notification) | 
|  | return; | 
|  |  | 
|  | url::Origin origin = url::Origin::Create(app_identifier.origin()); | 
|  | if (auto* devtools_context = GetDevToolsContext(app_identifier.origin())) { | 
|  | devtools_context->LogBackgroundServiceEvent( | 
|  | app_identifier.service_worker_registration_id(), | 
|  | blink::StorageKey::CreateFirstParty(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) { | 
|  | push_messaging::AppIdentifier 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) { | 
|  | render_process_id_ = render_process_id; | 
|  | push_messaging::AppIdentifier 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 = push_messaging::AppIdentifier::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; | 
|  | } | 
|  |  | 
|  | if (render_frame_host->GetLastCommittedOrigin().GetURL() != | 
|  | requesting_origin) { | 
|  | SubscribeEndWithError( | 
|  | std::move(callback), | 
|  | blink::mojom::PushRegistrationStatus::PERMISSION_DENIED); | 
|  | return; | 
|  | } | 
|  |  | 
|  | // It is OK to ignore `requesting_origin` because it will be calculated from | 
|  | // `render_frame_host` and we always use `requesting_origin` for | 
|  | // NOTIFICATIONS. | 
|  | profile_->GetPermissionController()->RequestPermissionFromCurrentDocument( | 
|  | render_frame_host, | 
|  | content::PermissionRequestDescription( | 
|  | content::PermissionDescriptorUtil:: | 
|  | CreatePermissionDescriptorForPermissionType( | 
|  | blink::PermissionType::NOTIFICATIONS), | 
|  | 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::SetSubscribeFromWorkerCallback( | 
|  | base::RepeatingCallback<void(int64_t)> callback) { | 
|  | subscribe_from_worker_callback_ = callback; | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::SubscribeFromWorker( | 
|  | const GURL& requesting_origin, | 
|  | int64_t service_worker_registration_id, | 
|  | int render_process_id, | 
|  | blink::mojom::PushSubscriptionOptionsPtr options, | 
|  | RegisterCallback register_callback) { | 
|  | if (subscribe_from_worker_callback_) { | 
|  | subscribe_from_worker_callback_->Run(service_worker_registration_id); | 
|  | } | 
|  |  | 
|  | render_process_id_ = render_process_id; | 
|  | push_messaging::AppIdentifier 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 = push_messaging::AppIdentifier::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; | 
|  | } | 
|  |  | 
|  | if (!options->user_visible_only) { | 
|  | origins_requesting_user_visible_requirement_bypass.insert( | 
|  | app_identifier.origin()); | 
|  | } | 
|  |  | 
|  | DoSubscribe( | 
|  | std::move(app_identifier), std::move(options), | 
|  | std::move(register_callback), | 
|  | /* render_process_id= */ -1, /* render_frame_id= */ -1, | 
|  | content::PermissionResult(blink::mojom::PermissionStatus::GRANTED, | 
|  | content::PermissionStatusSource::UNSPECIFIED)); | 
|  | } | 
|  |  | 
|  | blink::mojom::PermissionStatus PushMessagingServiceImpl::GetPermissionStatus( | 
|  | const GURL& origin, | 
|  | bool user_visible) { | 
|  | // Allows some origins to pass userVisibleOnly:false and allow Push API | 
|  | // usage, but deny all others that attempt. | 
|  | if (!user_visible) { | 
|  | if (notification_manager_.ShouldBypassNotificationPermissionRequirement( | 
|  | origin, /*requested_user_visible_only=*/!user_visible)) { | 
|  | return blink::mojom::PermissionStatus::GRANTED; | 
|  |  | 
|  | } else { | 
|  | return blink::mojom::PermissionStatus::DENIED; | 
|  | } | 
|  | } | 
|  |  | 
|  | const auto permission_descriptor = content::PermissionDescriptorUtil:: | 
|  | CreatePermissionDescriptorForPermissionType( | 
|  | blink::PermissionType::NOTIFICATIONS); | 
|  |  | 
|  | // 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. | 
|  | if (render_process_id_ != content::ChildProcessHost::kInvalidUniqueID) { | 
|  | return profile_->GetPermissionController()->GetPermissionStatusForWorker( | 
|  | permission_descriptor, | 
|  | content::RenderProcessHost::FromID(render_process_id_), | 
|  | url::Origin::Create(origin)); | 
|  | } else { | 
|  | return profile_->GetPermissionController() | 
|  | ->GetPermissionResultForOriginWithoutContext( | 
|  | permission_descriptor, url::Origin::Create(origin)) | 
|  | .status; | 
|  | } | 
|  | } | 
|  |  | 
|  | #if BUILDFLAG(IS_ANDROID) | 
|  | // static | 
|  | void PushMessagingServiceImpl::RegisterPrefs(PrefRegistrySimple* registry) { | 
|  | registry->RegisterTimePref(kNotificationsPermissionRevocationGracePeriodDate, | 
|  | base::Time()); | 
|  | } | 
|  |  | 
|  | static void | 
|  | JNI_PushMessagingServiceBridge_VerifyAndRevokeNotificationsPermission( | 
|  | JNIEnv* env, | 
|  | std::string& origin, | 
|  | std::string& profile_id, | 
|  | jboolean app_level_notifications_enabled) { | 
|  | ProfileManager* profile_manager = g_browser_process->profile_manager(); | 
|  | DCHECK(profile_manager); | 
|  |  | 
|  | profile_manager->LoadProfile( | 
|  | NotificationPlatformBridge::GetProfileBaseNameFromProfileId(profile_id), | 
|  | /*incognito=*/false, | 
|  | base::BindOnce(&PushMessagingServiceImpl::RevokePermissionIfPossible, | 
|  | GURL(origin), app_level_notifications_enabled, | 
|  | g_browser_process->local_state())); | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::RevokePermissionIfPossible( | 
|  | GURL origin, | 
|  | bool app_level_notifications_enabled, | 
|  | PrefService* prefs, | 
|  | Profile* profile) { | 
|  | if (app_level_notifications_enabled) { | 
|  | // Chrome has app-level Notifications permission. Reset the grace period | 
|  | // flag and continue as normal. | 
|  | prefs->ClearPref(kNotificationsPermissionRevocationGracePeriodDate); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (prefs->GetTime(kNotificationsPermissionRevocationGracePeriodDate) == | 
|  | base::Time()) { | 
|  | prefs->SetTime(kNotificationsPermissionRevocationGracePeriodDate, | 
|  | base::Time::Now()); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::TimeDelta permission_revocation_activated_duration = | 
|  | base::Time::Now() - | 
|  | prefs->GetTime(kNotificationsPermissionRevocationGracePeriodDate); | 
|  |  | 
|  | // The grace period that will be applied before site-level Notifications | 
|  | // permissions will be revoked and FCM unsubscribed. | 
|  | constexpr int kNotificationsRevocationGracePeriodInDays = 3; | 
|  |  | 
|  | if (permission_revocation_activated_duration.InDays() >= | 
|  | kNotificationsRevocationGracePeriodInDays) { | 
|  | content::PermissionController* permission_controller = | 
|  | profile->GetPermissionController(); | 
|  |  | 
|  | // As soon as permission is reset, | 
|  | // `PushMessagingServiceImpl::OnContentSettingChanged` is notified and it | 
|  | // revokes a push message registration token. | 
|  | permission_controller->ResetPermission(blink::PermissionType::NOTIFICATIONS, | 
|  | url::Origin::Create(origin)); | 
|  | } | 
|  | } | 
|  |  | 
|  | #endif | 
|  |  | 
|  | bool PushMessagingServiceImpl::SupportNonVisibleMessages() { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::DoSubscribe( | 
|  | push_messaging::AppIdentifier app_identifier, | 
|  | blink::mojom::PushSubscriptionOptionsPtr options, | 
|  | RegisterCallback register_callback, | 
|  | int render_process_id, | 
|  | int render_frame_id, | 
|  | content::PermissionResult permission_result) { | 
|  | if (permission_result.status != blink::mojom::PermissionStatus::GRANTED) { | 
|  | 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 std::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() /* endpoint */, std::nullopt /* expiration_time */, | 
|  | std::vector<uint8_t>() /* p256dh */, | 
|  | std::vector<uint8_t>() /* auth */, status); | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::DidSubscribe( | 
|  | const push_messaging::AppIdentifier& 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 = CreateEndpointFromChromeChannel(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 push_messaging::AppIdentifier& 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; | 
|  | } | 
|  |  | 
|  | PushMessagingAppIdentifier::PersistToPrefs(app_identifier, profile_); | 
|  | PushMessagingUnsubscribedEntry( | 
|  | app_identifier.origin(), app_identifier.service_worker_registration_id()) | 
|  | .DeleteFromPrefs(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) { | 
|  | push_messaging::AppIdentifier app_identifier = | 
|  | PushMessagingAppIdentifier::FindByServiceWorker( | 
|  | profile_, origin, service_worker_registration_id); | 
|  |  | 
|  | if (app_identifier.is_null()) { | 
|  | std::move(callback).Run(false /* is_valid */, GURL() /*endpoint*/, | 
|  | std::nullopt /* expiration_time */, | 
|  | std::vector<uint8_t>() /* p256dh */, | 
|  | std::vector<uint8_t>() /* auth */); | 
|  | return; | 
|  | } | 
|  |  | 
|  | const GURL endpoint = CreateEndpointFromChromeChannel(subscription_id); | 
|  | const std::string& app_id = app_identifier.app_id(); | 
|  | std::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 (push_messaging::AppIdentifier::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 std::optional<base::Time>& expiration_time, | 
|  | SubscriptionInfoCallback callback, | 
|  | bool is_valid) { | 
|  | if (!is_valid) { | 
|  | std::move(callback).Run(false /* is_valid */, GURL() /* endpoint */, | 
|  | std::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 std::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) { | 
|  | push_messaging::AppIdentifier 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))); | 
|  |  | 
|  | origins_requesting_user_visible_requirement_bypass.erase(origin); | 
|  | } | 
|  |  | 
|  | 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 | 
|  | // push_messaging::AppIdentifier 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). | 
|  | push_messaging::AppIdentifier app_identifier = | 
|  | PushMessagingAppIdentifier::FindByAppId(profile_, app_id); | 
|  | bool was_subscribed = !app_identifier.is_null(); | 
|  | if (was_subscribed) { | 
|  | PushMessagingAppIdentifier::DeleteFromPrefs(app_identifier, profile_); | 
|  | if (base::FeatureList::IsEnabled( | 
|  | features::kPushSubscriptionChangeEventOnResubscribe)) { | 
|  | switch (reason) { | 
|  | case blink::mojom::PushUnregistrationReason::PERMISSION_REVOKED_ABUSIVE: | 
|  | case blink::mojom::PushUnregistrationReason::PERMISSION_REVOKED: | 
|  | case blink::mojom::PushUnregistrationReason:: | 
|  | PERMISSION_REVOKED_DISRUPTIVE: | 
|  | // Store an UnsubscribedEntry for future resubscription. | 
|  | PushMessagingUnsubscribedEntry( | 
|  | app_identifier.origin(), | 
|  | app_identifier.service_worker_registration_id()) | 
|  | .PersistToPrefs(profile_); | 
|  | break; | 
|  | case blink::mojom::PushUnregistrationReason::UNKNOWN: | 
|  | case blink::mojom::PushUnregistrationReason::JAVASCRIPT_API: | 
|  | case blink::mojom::PushUnregistrationReason::DELIVERY_UNKNOWN_APP_ID: | 
|  | case blink::mojom::PushUnregistrationReason::DELIVERY_PERMISSION_DENIED: | 
|  | case blink::mojom::PushUnregistrationReason::DELIVERY_NO_SERVICE_WORKER: | 
|  | case blink::mojom::PushUnregistrationReason::GCM_STORE_RESET: | 
|  | case blink::mojom::PushUnregistrationReason:: | 
|  | SERVICE_WORKER_UNREGISTERED: | 
|  | case blink::mojom::PushUnregistrationReason::SUBSCRIBE_STORAGE_CORRUPT: | 
|  | case blink::mojom::PushUnregistrationReason:: | 
|  | GET_SUBSCRIPTION_STORAGE_CORRUPT: | 
|  | case blink::mojom::PushUnregistrationReason:: | 
|  | SERVICE_WORKER_DATABASE_WIPED: | 
|  | case blink::mojom::PushUnregistrationReason::SUBSCRIPTION_EXPIRED: | 
|  | case blink::mojom::PushUnregistrationReason::REFRESH_FINISHED: | 
|  | case blink::mojom::PushUnregistrationReason::NO_APP_LEVEL_PERMISSION: | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | // 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 (push_messaging::AppIdentifier::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 BUILDFLAG(IS_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) { | 
|  | DidUnsubscribe(std::string() /* app_id_when_instance_id */, was_subscribed); | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::DidDeleteID(const std::string& app_id, | 
|  | bool was_subscribed, | 
|  | InstanceID::Result 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::SingleThreadTaskRunner::GetCurrentDefault()->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) { | 
|  | PushMessagingUnsubscribedEntry(origin, service_worker_registration_id) | 
|  | .DeleteFromPrefs(profile_); | 
|  | const push_messaging::AppIdentifier& 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() { | 
|  | PushMessagingUnsubscribedEntry::DeleteAllFromPrefs(profile_); | 
|  |  | 
|  | std::vector<push_messaging::AppIdentifier> 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 push_messaging::AppIdentifier& 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<push_messaging::AppIdentifier> all_app_identifiers = | 
|  | PushMessagingAppIdentifier::GetAll(profile_); | 
|  |  | 
|  | std::vector<PushMessagingUnsubscribedEntry> unsubscribed_entries; | 
|  | if (base::FeatureList::IsEnabled( | 
|  | features::kPushSubscriptionChangeEventOnResubscribe) && | 
|  | // We don't want to trigger the event to possibly multiple service workers | 
|  | // and origins if the user changed a wildcard setting. | 
|  | primary_pattern.MatchesSingleOrigin()) { | 
|  | unsubscribed_entries = PushMessagingUnsubscribedEntry::GetAll(profile_); | 
|  | } | 
|  | base::RepeatingClosure barrier_closure = base::BarrierClosure( | 
|  | all_app_identifiers.size() + unsubscribed_entries.size(), | 
|  | content_setting_changed_callback_for_testing_.is_null() | 
|  | ? base::DoNothing() | 
|  | : content_setting_changed_callback_for_testing_); | 
|  |  | 
|  | for (const push_messaging::AppIdentifier& app_identifier : | 
|  | all_app_identifiers) { | 
|  | if (!primary_pattern.Matches(app_identifier.origin())) { | 
|  | barrier_closure.Run(); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | bool user_visible = | 
|  | !base::Contains(origins_requesting_user_visible_requirement_bypass, | 
|  | app_identifier.origin()); | 
|  |  | 
|  | if (IsPermissionSet(app_identifier.origin(), user_visible)) { | 
|  | barrier_closure.Run(); | 
|  | continue; | 
|  | } | 
|  | if (!permissions::PermissionUmaUtil::ScopedRevocationReporter:: | 
|  | IsInstanceInScope()) { | 
|  | permissions::PermissionUmaUtil::PermissionRevoked( | 
|  | ContentSettingsType::NOTIFICATIONS, | 
|  | permissions::PermissionSourceUI::UNIDENTIFIED, | 
|  | app_identifier.origin(), profile_); | 
|  | } | 
|  | UnexpectedChange(app_identifier, | 
|  | blink::mojom::PushUnregistrationReason::PERMISSION_REVOKED, | 
|  | barrier_closure); | 
|  | } | 
|  |  | 
|  | base::UmaHistogramCounts1000("PushMessaging.NumUnsubscribedEntries", | 
|  | unsubscribed_entries.size()); | 
|  |  | 
|  | int num_fired = 0; | 
|  | for (const PushMessagingUnsubscribedEntry& unsubscribed_entry : | 
|  | unsubscribed_entries) { | 
|  | if (!primary_pattern.Matches(unsubscribed_entry.origin())) { | 
|  | barrier_closure.Run(); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | bool user_visible = | 
|  | !base::Contains(origins_requesting_user_visible_requirement_bypass, | 
|  | unsubscribed_entry.origin()); | 
|  |  | 
|  | if (!IsPermissionSet(unsubscribed_entry.origin(), user_visible)) { | 
|  | barrier_closure.Run(); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | FirePushSubscriptionChange( | 
|  | unsubscribed_entry.origin(), | 
|  | unsubscribed_entry.service_worker_registration_id(), barrier_closure, | 
|  | nullptr, nullptr); | 
|  | ++num_fired; | 
|  | } | 
|  |  | 
|  | base::UmaHistogramCounts100( | 
|  | "PushMessaging." | 
|  | "PushSubscriptionChangeForNotificationPermissionChangeFired", | 
|  | num_fired); | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::UnexpectedUnsubscribe( | 
|  | const push_messaging::AppIdentifier& 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 BUILDFLAG(IS_ANDROID) | 
|  | need_sender_id = | 
|  | !push_messaging::AppIdentifier::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 push_messaging::AppIdentifier& 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 push_messaging::AppIdentifier& 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 std::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 GURL& origin, | 
|  | int64_t service_worker_registration_id, | 
|  | base::OnceClosure completed_closure, | 
|  | blink::mojom::PushSubscriptionPtr new_subscription, | 
|  | blink::mojom::PushSubscriptionPtr old_subscription) { | 
|  | CHECK(features::IsPushSubscriptionChangeEventEnabled()); | 
|  |  | 
|  | // Ensure |completed_closure| is run after this function | 
|  | base::ScopedClosureRunner scoped_closure(std::move(completed_closure)); | 
|  |  | 
|  | profile_->FirePushSubscriptionChangeEvent( | 
|  | origin, service_worker_registration_id, std::move(new_subscription), | 
|  | std::move(old_subscription), base::DoNothing()); | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::FirePushSubscriptionChangeForAppIdentifier( | 
|  | const push_messaging::AppIdentifier& app_identifier, | 
|  | base::OnceClosure completed_closure, | 
|  | blink::mojom::PushSubscriptionPtr new_subscription, | 
|  | blink::mojom::PushSubscriptionPtr old_subscription) { | 
|  | if (app_identifier.is_null()) { | 
|  | std::move(completed_closure).Run(); | 
|  | return; | 
|  | } | 
|  |  | 
|  | FirePushSubscriptionChange( | 
|  | app_identifier.origin(), app_identifier.service_worker_registration_id(), | 
|  | std::move(completed_closure), std::move(new_subscription), | 
|  | std::move(old_subscription)); | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::DidGetSenderIdUnexpectedUnsubscribe( | 
|  | const push_messaging::AppIdentifier& app_identifier, | 
|  | blink::mojom::PushUnregistrationReason reason, | 
|  | UnregisterCallback callback, | 
|  | const std::string& sender_id) { | 
|  | // Unsubscribe the push_messaging::AppIdentifier 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(push_messaging::kAppIdentifierPrefix); | 
|  | HostContentSettingsMapFactory::GetForProfile(profile_)->RemoveObserver(this); | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::OnAppTerminating() { | 
|  | 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) { | 
|  | CHECK(base::FeatureList::IsEnabled( | 
|  | features::kPushSubscriptionChangeEventOnInvalidation)) | 
|  | << "It is not allowed to call this method when " | 
|  | "features::kPushSubscriptionChangeEventOnInvalidation is disabled."; | 
|  | push_messaging::AppIdentifier 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( | 
|  | push_messaging::AppIdentifier old_app_identifier, | 
|  | const std::string& sender_id) { | 
|  | CHECK(base::FeatureList::IsEnabled( | 
|  | features::kPushSubscriptionChangeEventOnInvalidation)); | 
|  |  | 
|  | GetPushSubscriptionFromAppIdentifier( | 
|  | old_app_identifier, | 
|  | base::BindOnce(&PushMessagingServiceImpl::StartRefresh, | 
|  | weak_factory_.GetWeakPtr(), old_app_identifier, | 
|  | sender_id)); | 
|  | } | 
|  |  | 
|  | void PushMessagingServiceImpl::StartRefresh( | 
|  | push_messaging::AppIdentifier old_app_identifier, | 
|  | const std::string& sender_id, | 
|  | blink::mojom::PushSubscriptionPtr old_subscription) { | 
|  | CHECK(base::FeatureList::IsEnabled( | 
|  | features::kPushSubscriptionChangeEventOnInvalidation)); | 
|  |  | 
|  | // 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 | 
|  | push_messaging::AppIdentifier new_app_identifier = | 
|  | push_messaging::AppIdentifier::Generate( | 
|  | old_app_identifier.origin(), | 
|  | old_app_identifier.service_worker_registration_id(), | 
|  | std::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( | 
|  | push_messaging::AppIdentifier 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, | 
|  | push_messaging::AppIdentifier ai, const std::string& registration_id, | 
|  | const GURL& endpoint, const std::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); | 
|  |  | 
|  | blink::mojom::PermissionStatus permission_status = | 
|  | blink::mojom::PermissionStatus::GRANTED; | 
|  |  | 
|  | if (!options->user_visible_only) { | 
|  | if (notification_manager_.ShouldBypassUserVisibleOnlyRequirement( | 
|  | app_identifier.origin(), options->user_visible_only)) { | 
|  | origins_requesting_user_visible_requirement_bypass.insert( | 
|  | app_identifier.origin()); | 
|  | } else { | 
|  | permission_status = blink::mojom::PermissionStatus::DENIED; | 
|  | } | 
|  | } | 
|  | // 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::PermissionResult(permission_status, | 
|  | content::PermissionStatusSource::UNSPECIFIED)); | 
|  | } | 
|  |  | 
|  | 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 std::optional<base::Time>& expiration_time, | 
|  | const std::vector<uint8_t>& p256dh, | 
|  | const std::vector<uint8_t>& auth, | 
|  | blink::mojom::PushRegistrationStatus status) { | 
|  | CHECK(base::FeatureList::IsEnabled( | 
|  | features::kPushSubscriptionChangeEventOnInvalidation)); | 
|  |  | 
|  | // TODO(crbug.com/40146635): 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); | 
|  |  | 
|  | push_messaging::AppIdentifier 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(); | 
|  |  | 
|  | FirePushSubscriptionChangeForAppIdentifier( | 
|  | 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() /* 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 push_messaging::AppIdentifier& 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); | 
|  | } | 
|  |  | 
|  | 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 (push_messaging::AppIdentifier::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; | 
|  | } |