blob: fe7a0b06335920e79a97e70d45d1301091de88cb [file] [log] [blame]
// 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 "content/browser/push_messaging/push_messaging_manager.h"
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/command_line.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/metrics/histogram_macros.h"
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/time/time.h"
#include "content/browser/bad_message.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/browser/service_worker/service_worker_context_core.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_descriptor_util.h"
#include "content/public/browser/permission_request_description.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/common/content_switches.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/push_messaging/push_messaging.mojom.h"
#include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom.h"
#include "url/origin.h"
namespace content {
namespace {
// Chrome currently does not support the Push API in incognito.
const char kIncognitoPushUnsupportedMessage[] =
"Chrome currently does not support the Push API in incognito mode "
"(https://crbug.com/401439). There is deliberately no way to "
"feature-detect this, since incognito mode needs to be undetectable by "
"websites.";
// These UMA methods are called from the SW and/or UI threads. Racey but ok, see
// https://groups.google.com/a/chromium.org/d/msg/chromium-dev/FNzZRJtN2aw/Aw0CWAXJJ1kJ
void RecordRegistrationStatus(blink::mojom::PushRegistrationStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
UMA_HISTOGRAM_ENUMERATION("PushMessaging.RegistrationStatus", status);
}
void RecordUnregistrationStatus(blink::mojom::PushUnregistrationStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
UMA_HISTOGRAM_ENUMERATION("PushMessaging.UnregistrationStatus", status);
}
void RecordGetRegistrationStatus(
blink::mojom::PushGetRegistrationStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
UMA_HISTOGRAM_ENUMERATION("PushMessaging.GetRegistrationStatus", status);
}
const char* PushUnregistrationStatusToString(
blink::mojom::PushUnregistrationStatus status) {
switch (status) {
case blink::mojom::PushUnregistrationStatus::SUCCESS_UNREGISTERED:
return "Unregistration successful - from push service";
case blink::mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED:
return "Unregistration successful - was not registered";
case blink::mojom::PushUnregistrationStatus::PENDING_NETWORK_ERROR:
return "Unregistration pending - a network error occurred, but it will "
"be retried until it succeeds";
case blink::mojom::PushUnregistrationStatus::NO_SERVICE_WORKER:
return "Unregistration failed - no Service Worker";
case blink::mojom::PushUnregistrationStatus::SERVICE_NOT_AVAILABLE:
return "Unregistration failed - push service not available";
case blink::mojom::PushUnregistrationStatus::PENDING_SERVICE_ERROR:
return "Unregistration pending - a push service error occurred, but it "
"will be retried until it succeeds";
case blink::mojom::PushUnregistrationStatus::STORAGE_ERROR:
return "Unregistration failed - storage error";
case blink::mojom::PushUnregistrationStatus::NETWORK_ERROR:
return "Unregistration failed - could not connect to push server";
}
NOTREACHED();
}
// Returns application_server_key if non-empty, otherwise checks if
// stored_sender_id may be used as a fallback and if so, returns
// stored_sender_id instead.
//
// This is in order to support the legacy way of subscribing from a service
// worker (first subscribe from the document using a gcm_sender_id set in the
// manifest, and then subscribe from the service worker with no key).
//
// An empty string will be returned if application_server_key is empty and the
// fallback is not a numeric gcm sender id.
std::string FixSenderInfo(const std::string& application_server_key,
const std::string& stored_sender_id) {
if (!application_server_key.empty())
return application_server_key;
if (base::ContainsOnlyChars(stored_sender_id, "0123456789"))
return stored_sender_id;
return std::string();
}
bool IsRequestFromDocument(int render_frame_id) {
return render_frame_id != ChildProcessHost::kInvalidUniqueID;
}
} // namespace
struct PushMessagingManager::RegisterData {
RegisterData() = default;
RegisterData(RegisterData&& other) = default;
blink::StorageKey requesting_storage_key{};
int64_t service_worker_registration_id{0};
std::optional<std::string> existing_subscription_id;
blink::mojom::PushSubscriptionOptionsPtr options;
SubscribeCallback callback;
// True if the call to register was made with a user gesture.
bool user_gesture;
};
PushMessagingManager::PushMessagingManager(
RenderProcessHost& render_process_host,
int render_frame_id,
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context)
: render_process_host_(render_process_host),
render_frame_id_(render_frame_id),
service_worker_context_(std::move(service_worker_context)),
is_incognito_(
render_process_host_->GetBrowserContext()->IsOffTheRecord()),
service_available_(!!GetService()) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
}
PushMessagingManager::~PushMessagingManager() {}
void PushMessagingManager::AddPushMessagingReceiver(
mojo::PendingReceiver<blink::mojom::PushMessaging> receiver) {
receivers_.Add(this, std::move(receiver));
}
// Subscribe methods, merged in order of use.
// -----------------------------------------------------------------------------
void PushMessagingManager::Subscribe(
int64_t service_worker_registration_id,
blink::mojom::PushSubscriptionOptionsPtr options,
bool user_gesture,
SubscribeCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(options);
RegisterData data;
data.service_worker_registration_id = service_worker_registration_id;
data.callback = std::move(callback);
data.options = std::move(options);
data.user_gesture = user_gesture;
scoped_refptr<ServiceWorkerRegistration> service_worker_registration =
service_worker_context_->GetLiveRegistration(
data.service_worker_registration_id);
if (!service_worker_registration ||
!service_worker_registration->active_version()) {
SendSubscriptionError(
std::move(data),
blink::mojom::PushRegistrationStatus::NO_SERVICE_WORKER);
return;
}
// The renderer should have checked and disallowed the request for fenced
// frames and thrown an exception in blink::PushManager. Report a bad message
// if the renderer if the renderer side check didn't happen for some reason.
if (service_worker_registration->ancestor_frame_type() ==
blink::mojom::AncestorFrameType::kFencedFrame) {
bad_message::ReceivedBadMessage(render_process_host_->GetDeprecatedID(),
bad_message::PMM_SUBSCRIBE_IN_FENCED_FRAME);
return;
}
const blink::StorageKey& storage_key = service_worker_registration->key();
if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanAccessDataForOrigin(
render_process_host_->GetDeprecatedID(), storage_key.origin())) {
bad_message::ReceivedBadMessage(&*render_process_host_,
bad_message::PMM_SUBSCRIBE_INVALID_ORIGIN);
return;
}
data.requesting_storage_key = storage_key;
DCHECK(!(data.options->application_server_key.empty() &&
IsRequestFromDocument(render_frame_id_)));
int64_t registration_id = data.service_worker_registration_id;
service_worker_context_->GetRegistrationUserData(
registration_id,
{kPushRegistrationIdServiceWorkerKey, kPushSenderIdServiceWorkerKey},
base::BindOnce(&PushMessagingManager::DidCheckForExistingRegistration,
weak_factory_.GetWeakPtr(), std::move(data)));
}
void PushMessagingManager::DidCheckForExistingRegistration(
RegisterData data,
const std::vector<std::string>& subscription_id_and_sender_id,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Validate the stored subscription against the subscription request made by
// the developer. The authorized entity must match.
if (service_worker_status == blink::ServiceWorkerStatusCode::kOk) {
DCHECK_EQ(2u, subscription_id_and_sender_id.size());
const std::string& subscription_id = subscription_id_and_sender_id[0];
const std::string& stored_sender_id = subscription_id_and_sender_id[1];
const std::string application_server_key_string(
data.options->application_server_key.begin(),
data.options->application_server_key.end());
std::string fixed_sender_id(
FixSenderInfo(application_server_key_string, stored_sender_id));
if (fixed_sender_id.empty()) {
SendSubscriptionError(std::move(data),
blink::mojom::PushRegistrationStatus::NO_SENDER_ID);
return;
}
if (fixed_sender_id != stored_sender_id) {
SendSubscriptionError(
std::move(data),
blink::mojom::PushRegistrationStatus::SENDER_ID_MISMATCH);
return;
}
data.existing_subscription_id = subscription_id;
}
// TODO(peter): Handle failures other than
// blink::ServiceWorkerStatusCode::kErrorNotFound by rejecting
// the subscription algorithm instead of trying to subscribe.
if (!data.options->application_server_key.empty()) {
Register(std::move(data));
} else {
// No |application_server_key| was provided by the developer. Fall back to
// checking whether a previous subscription did identify a sender.
int64_t registration_id = data.service_worker_registration_id;
service_worker_context_->GetRegistrationUserData(
registration_id, {kPushSenderIdServiceWorkerKey},
base::BindOnce(&PushMessagingManager::DidGetSenderIdFromStorage,
weak_factory_.GetWeakPtr(), std::move(data)));
}
}
void PushMessagingManager::DidGetSenderIdFromStorage(
RegisterData data,
const std::vector<std::string>& stored_sender_id,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
SendSubscriptionError(std::move(data),
blink::mojom::PushRegistrationStatus::NO_SENDER_ID);
return;
}
DCHECK_EQ(1u, stored_sender_id.size());
// We should only be here because no sender info was supplied to subscribe().
DCHECK(data.options->application_server_key.empty());
const std::string application_server_key_string(
std::string(data.options->application_server_key.begin(),
data.options->application_server_key.end()));
std::string fixed_sender_id(
FixSenderInfo(application_server_key_string, stored_sender_id[0]));
if (fixed_sender_id.empty()) {
SendSubscriptionError(std::move(data),
blink::mojom::PushRegistrationStatus::NO_SENDER_ID);
return;
}
data.options->application_server_key =
std::vector<uint8_t>(fixed_sender_id.begin(), fixed_sender_id.end());
Register(std::move(data));
}
void PushMessagingManager::Register(PushMessagingManager::RegisterData data) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PushMessagingService* push_service = GetService();
if (!push_service) {
if (!is_incognito_) {
// This might happen if InstanceIDProfileService::IsInstanceIDEnabled
// returns false because the Instance ID kill switch was enabled.
SendSubscriptionError(
std::move(data),
blink::mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE);
} else {
// Prevent websites from detecting incognito mode, by emulating what would
// have happened if we had a PushMessagingService available.
if (!IsRequestFromDocument(render_frame_id_) ||
!data.options->user_visible_only) {
// Throw a permission denied error under the same circumstances.
SendSubscriptionError(
std::move(data),
blink::mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED);
} else {
RenderFrameHostImpl* render_frame_host_impl =
RenderFrameHostImpl::FromID(render_process_host_->GetDeprecatedID(),
render_frame_id_);
if (render_frame_host_impl) {
render_frame_host_impl->AddMessageToConsole(
blink::mojom::ConsoleMessageLevel::kError,
kIncognitoPushUnsupportedMessage);
// Request notifications permission (which will fail, since
// notifications aren't supported in incognito), so the website can't
// detect whether incognito is active.
bool user_gesture = data.user_gesture;
DCHECK_EQ(data.requesting_storage_key,
render_frame_host_impl->GetStorageKey());
render_frame_host_impl->GetBrowserContext()
->GetPermissionController()
->RequestPermissionFromCurrentDocument(
render_frame_host_impl,
PermissionRequestDescription(
PermissionDescriptorUtil::
CreatePermissionDescriptorForPermissionType(
blink::PermissionType::NOTIFICATIONS),
user_gesture),
base::BindOnce(
&PushMessagingManager::DidRequestPermissionInIncognito,
AsWeakPtr(), std::move(data)));
}
}
}
return;
}
int64_t registration_id = data.service_worker_registration_id;
url::Origin requesting_origin = data.requesting_storage_key.origin();
bool user_gesture = data.user_gesture;
auto options = data.options->Clone();
if (IsRequestFromDocument(render_frame_id_)) {
push_service->SubscribeFromDocument(
requesting_origin.GetURL(), registration_id,
render_process_host_->GetDeprecatedID(), render_frame_id_,
std::move(options), user_gesture,
base::BindOnce(&PushMessagingManager::DidRegister, AsWeakPtr(),
std::move(data)));
} else {
push_service->SubscribeFromWorker(
requesting_origin.GetURL(), registration_id,
render_process_host_->GetDeprecatedID(), std::move(options),
base::BindOnce(&PushMessagingManager::DidRegister, AsWeakPtr(),
std::move(data)));
}
}
void PushMessagingManager::DidRequestPermissionInIncognito(
RegisterData data,
PermissionResult permission_result) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// Notification permission should always be denied in incognito.
DCHECK_EQ(blink::mojom::PermissionStatus::DENIED, permission_result.status);
SendSubscriptionError(
std::move(data),
blink::mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED);
}
// TODO(crbug.com/40139581): Handle expiration_time that is passed from push
// service check if |expiration_time| is valid before saving it in |data| and
// passing it back in SendSubscriptionSuccess.
void PushMessagingManager::DidRegister(
RegisterData data,
const std::string& push_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) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
// TODO(crbug.com/41275327): Handle the case where |push_subscription_id| and
// |data.existing_subscription_id| are not the same. Right now we just
// override the old subscription ID and encryption information.
const bool subscription_changed =
data.existing_subscription_id.has_value() &&
data.existing_subscription_id.value() != push_subscription_id;
if (status ==
blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE) {
PersistRegistration(
std::move(data), push_subscription_id, endpoint, expiration_time,
p256dh, auth,
subscription_changed
? blink::mojom::PushRegistrationStatus::
SUCCESS_NEW_SUBSCRIPTION_FROM_PUSH_SERVICE
: blink::mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE);
} else {
// TODO(crbug.com/41275327): for invalid |expiration_time| send a
// subscription error with a new PushRegistrationStatus
SendSubscriptionError(std::move(data), status);
}
}
void PushMessagingManager::PersistRegistration(
RegisterData data,
const std::string& push_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) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
blink::StorageKey storage_key = data.requesting_storage_key;
int64_t registration_id = data.service_worker_registration_id;
std::string application_server_key(
std::string(data.options->application_server_key.begin(),
data.options->application_server_key.end()));
service_worker_context_->StoreRegistrationUserData(
registration_id, std::move(storage_key),
{{kPushRegistrationIdServiceWorkerKey, push_subscription_id},
{kPushSenderIdServiceWorkerKey, application_server_key}},
base::BindOnce(&PushMessagingManager::DidPersistRegistration,
weak_factory_.GetWeakPtr(), std::move(data), endpoint,
expiration_time, p256dh, auth, status));
}
void PushMessagingManager::DidPersistRegistration(
RegisterData data,
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 push_registration_status,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (service_worker_status == blink::ServiceWorkerStatusCode::kOk) {
SendSubscriptionSuccess(std::move(data), push_registration_status, endpoint,
expiration_time, p256dh, auth);
} else {
// TODO(johnme): Unregister, so PushMessagingServiceImpl can decrease count.
SendSubscriptionError(std::move(data),
blink::mojom::PushRegistrationStatus::STORAGE_ERROR);
}
}
void PushMessagingManager::SendSubscriptionError(
RegisterData data,
blink::mojom::PushRegistrationStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(data.callback).Run(status, nullptr /* subscription */);
RecordRegistrationStatus(status);
}
void PushMessagingManager::SendSubscriptionSuccess(
RegisterData data,
blink::mojom::PushRegistrationStatus status,
const GURL& endpoint,
const std::optional<base::Time>& expiration_time,
const std::vector<uint8_t>& p256dh,
const std::vector<uint8_t>& auth) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!service_available_) {
// This shouldn't be possible in incognito mode, since we've already checked
// that we have an existing registration. Hence it's ok to throw an error.
DCHECK(!is_incognito_);
SendSubscriptionError(
std::move(data),
blink::mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE);
return;
}
std::move(data.callback)
.Run(status, blink::mojom::PushSubscription::New(
endpoint, expiration_time, std::move(data.options),
p256dh, auth));
RecordRegistrationStatus(status);
}
// Unsubscribe methods, merged in order of use.
// -----------------------------------------------------------------------------
void PushMessagingManager::Unsubscribe(int64_t service_worker_registration_id,
UnsubscribeCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_refptr<ServiceWorkerRegistration> service_worker_registration =
service_worker_context_->GetLiveRegistration(
service_worker_registration_id);
if (!service_worker_registration) {
DidUnregister(std::move(callback),
blink::mojom::PushUnregistrationStatus::NO_SERVICE_WORKER);
return;
}
const url::Origin& origin = service_worker_registration->key().origin();
if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanAccessDataForOrigin(
render_process_host_->GetDeprecatedID(), origin)) {
bad_message::ReceivedBadMessage(
&*render_process_host_, bad_message::PMM_UNSUBSCRIBE_INVALID_ORIGIN);
return;
}
service_worker_context_->GetRegistrationUserData(
service_worker_registration_id, {kPushSenderIdServiceWorkerKey},
base::BindOnce(&PushMessagingManager::UnsubscribeHavingGottenSenderId,
weak_factory_.GetWeakPtr(), std::move(callback),
service_worker_registration_id, origin));
}
void PushMessagingManager::UnsubscribeHavingGottenSenderId(
UnsubscribeCallback callback,
int64_t service_worker_registration_id,
const url::Origin& requesting_origin,
const std::vector<std::string>& sender_ids,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::string sender_id;
if (service_worker_status == blink::ServiceWorkerStatusCode::kOk) {
DCHECK_EQ(1u, sender_ids.size());
sender_id = sender_ids[0];
}
PushMessagingService* push_service = GetService();
if (!push_service) {
// This shouldn't be possible in incognito mode, since we've already checked
// that we have an existing registration. Hence it's ok to throw an error.
DCHECK(!is_incognito_);
DidUnregister(
std::move(callback),
blink::mojom::PushUnregistrationStatus::SERVICE_NOT_AVAILABLE);
return;
}
push_service->Unsubscribe(
blink::mojom::PushUnregistrationReason::JAVASCRIPT_API,
requesting_origin.GetURL(), service_worker_registration_id, sender_id,
base::BindOnce(&PushMessagingManager::DidUnregister, AsWeakPtr(),
std::move(callback)));
}
void PushMessagingManager::DidUnregister(
UnsubscribeCallback callback,
blink::mojom::PushUnregistrationStatus unregistration_status) {
// Only called from SW thread, but would be safe to call from UI thread.
DCHECK_CURRENTLY_ON(BrowserThread::UI);
switch (unregistration_status) {
case blink::mojom::PushUnregistrationStatus::SUCCESS_UNREGISTERED:
case blink::mojom::PushUnregistrationStatus::PENDING_NETWORK_ERROR:
case blink::mojom::PushUnregistrationStatus::PENDING_SERVICE_ERROR:
std::move(callback).Run(blink::mojom::PushErrorType::NONE,
true /* did_unsubscribe */,
std::nullopt /* error_message */);
break;
case blink::mojom::PushUnregistrationStatus::SUCCESS_WAS_NOT_REGISTERED:
std::move(callback).Run(blink::mojom::PushErrorType::NONE,
false /* did_unsubscribe */,
std::nullopt /* error_message */);
break;
case blink::mojom::PushUnregistrationStatus::NO_SERVICE_WORKER:
case blink::mojom::PushUnregistrationStatus::SERVICE_NOT_AVAILABLE:
case blink::mojom::PushUnregistrationStatus::STORAGE_ERROR:
std::move(callback).Run(blink::mojom::PushErrorType::ABORT, false,
std::string(PushUnregistrationStatusToString(
unregistration_status)) /* error_message */);
break;
case blink::mojom::PushUnregistrationStatus::NETWORK_ERROR:
NOTREACHED();
}
RecordUnregistrationStatus(unregistration_status);
}
// GetSubscription methods, merged in order of use.
// -----------------------------------------------------------------------------
void PushMessagingManager::GetSubscription(
int64_t service_worker_registration_id,
GetSubscriptionCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
scoped_refptr<ServiceWorkerRegistration> registration =
service_worker_context_->GetLiveRegistration(
service_worker_registration_id);
if (registration) {
if (!ChildProcessSecurityPolicyImpl::GetInstance()->CanAccessDataForOrigin(
render_process_host_->GetDeprecatedID(),
registration->key().origin())) {
bad_message::ReceivedBadMessage(
&*render_process_host_,
bad_message::PMM_GET_SUBSCRIPTION_INVALID_ORIGIN);
return;
}
}
service_worker_context_->GetRegistrationUserData(
service_worker_registration_id,
{kPushRegistrationIdServiceWorkerKey, kPushSenderIdServiceWorkerKey},
base::BindOnce(&PushMessagingManager::DidGetSubscription,
weak_factory_.GetWeakPtr(), std::move(callback),
service_worker_registration_id));
}
void PushMessagingManager::DidGetSubscription(
GetSubscriptionCallback callback,
int64_t service_worker_registration_id,
const std::vector<std::string>&
push_subscription_id_and_application_server_key,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
blink::mojom::PushGetRegistrationStatus get_status =
blink::mojom::PushGetRegistrationStatus::STORAGE_ERROR;
switch (service_worker_status) {
case blink::ServiceWorkerStatusCode::kOk: {
DCHECK_EQ(2u, push_subscription_id_and_application_server_key.size());
const std::string& push_subscription_id =
push_subscription_id_and_application_server_key[0];
const std::string& application_server_key =
push_subscription_id_and_application_server_key[1];
if (!service_available_) {
// Return not found in incognito mode, so websites can't detect it.
get_status = is_incognito_ ? blink::mojom::PushGetRegistrationStatus::
INCOGNITO_REGISTRATION_NOT_FOUND
: blink::mojom::PushGetRegistrationStatus::
SERVICE_NOT_AVAILABLE;
break;
}
scoped_refptr<ServiceWorkerRegistration> registration =
service_worker_context_->GetLiveRegistration(
service_worker_registration_id);
if (!registration) {
get_status =
blink::mojom::PushGetRegistrationStatus::NO_LIVE_SERVICE_WORKER;
break;
}
const url::Origin& origin = registration->key().origin();
GetSubscriptionInfo(
origin, service_worker_registration_id, application_server_key,
push_subscription_id,
base::BindOnce(&PushMessagingManager::GetSubscriptionDidGetInfo,
AsWeakPtr(), std::move(callback), origin,
service_worker_registration_id,
application_server_key));
return;
}
case blink::ServiceWorkerStatusCode::kErrorNotFound: {
get_status =
blink::mojom::PushGetRegistrationStatus::REGISTRATION_NOT_FOUND;
break;
}
case blink::ServiceWorkerStatusCode::kErrorFailed: {
get_status = blink::mojom::PushGetRegistrationStatus::STORAGE_ERROR;
break;
}
case blink::ServiceWorkerStatusCode::kErrorAbort:
case blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed:
case blink::ServiceWorkerStatusCode::kErrorProcessNotFound:
case blink::ServiceWorkerStatusCode::kErrorExists:
case blink::ServiceWorkerStatusCode::kErrorInstallWorkerFailed:
case blink::ServiceWorkerStatusCode::kErrorActivateWorkerFailed:
case blink::ServiceWorkerStatusCode::kErrorIpcFailed:
case blink::ServiceWorkerStatusCode::kErrorNetwork:
case blink::ServiceWorkerStatusCode::kErrorSecurity:
case blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected:
case blink::ServiceWorkerStatusCode::kErrorState:
case blink::ServiceWorkerStatusCode::kErrorTimeout:
case blink::ServiceWorkerStatusCode::kErrorScriptEvaluateFailed:
case blink::ServiceWorkerStatusCode::kErrorDiskCache:
case blink::ServiceWorkerStatusCode::kErrorRedundant:
case blink::ServiceWorkerStatusCode::kErrorDisallowed:
case blink::ServiceWorkerStatusCode::kErrorInvalidArguments:
case blink::ServiceWorkerStatusCode::kErrorStorageDisconnected:
case blink::ServiceWorkerStatusCode::kErrorStorageDataCorrupted: {
DUMP_WILL_BE_NOTREACHED()
<< "Got unexpected error code: "
<< static_cast<uint32_t>(service_worker_status) << " "
<< blink::ServiceWorkerStatusToString(service_worker_status);
get_status = blink::mojom::PushGetRegistrationStatus::STORAGE_ERROR;
break;
}
}
std::move(callback).Run(get_status, nullptr /* subscription */);
RecordGetRegistrationStatus(get_status);
}
void PushMessagingManager::GetSubscriptionDidGetInfo(
GetSubscriptionCallback callback,
const url::Origin& origin,
int64_t service_worker_registration_id,
const std::string& application_server_key,
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) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (is_valid) {
auto options = blink::mojom::PushSubscriptionOptions::New();
// Chrome rejects subscription requests with userVisibleOnly false, so it
// must have been true. TODO(harkness): If Chrome starts accepting silent
// push subscriptions with userVisibleOnly false, the bool will need to be
// stored.
options->user_visible_only = true;
options->application_server_key = std::vector<uint8_t>(
application_server_key.begin(), application_server_key.end());
blink::mojom::PushGetRegistrationStatus status =
blink::mojom::PushGetRegistrationStatus::SUCCESS;
std::move(callback).Run(status, blink::mojom::PushSubscription::New(
endpoint, expiration_time,
std::move(options), p256dh, auth));
RecordGetRegistrationStatus(status);
} else {
PushMessagingService* push_service = GetService();
if (!push_service) {
// Shouldn't be possible to have a stored push subscription in a profile
// with no push service, but this case can occur when the renderer is
// shutting down.
std::move(callback).Run(
blink::mojom::PushGetRegistrationStatus::RENDERER_SHUTDOWN,
nullptr /* subscription */);
return;
}
// Uh-oh! Although there was a cached subscription in the Service Worker
// database, it did not have matching counterparts in the
// PushMessagingAppIdentifier map and/or GCM Store. Unsubscribe to fix this
// inconsistency.
blink::mojom::PushGetRegistrationStatus status =
blink::mojom::PushGetRegistrationStatus::STORAGE_CORRUPT;
push_service->Unsubscribe(
blink::mojom::PushUnregistrationReason::
GET_SUBSCRIPTION_STORAGE_CORRUPT,
origin.GetURL(), service_worker_registration_id, application_server_key,
base::BindOnce(&PushMessagingManager::GetSubscriptionDidUnsubscribe,
AsWeakPtr(), std::move(callback), status));
RecordGetRegistrationStatus(status);
}
}
void PushMessagingManager::GetSubscriptionDidUnsubscribe(
GetSubscriptionCallback callback,
blink::mojom::PushGetRegistrationStatus get_status,
blink::mojom::PushUnregistrationStatus unsubscribe_status) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
std::move(callback).Run(get_status, nullptr /* subscription */);
}
void PushMessagingManager::GetSubscriptionInfo(
const url::Origin& origin,
int64_t service_worker_registration_id,
const std::string& sender_id,
const std::string& push_subscription_id,
PushMessagingService::SubscriptionInfoCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
PushMessagingService* push_service = GetService();
if (!push_service) {
std::move(callback).Run(false /* is_valid */, GURL() /* endpoint */,
std::nullopt /* expiration_time */,
std::vector<uint8_t>() /* p256dh */,
std::vector<uint8_t>() /* auth */);
return;
}
push_service->GetSubscriptionInfo(origin.GetURL(),
service_worker_registration_id, sender_id,
push_subscription_id, std::move(callback));
}
PushMessagingService* PushMessagingManager::GetService() {
return render_process_host_->GetBrowserContext()->GetPushMessagingService();
}
} // namespace content