blob: 605a4b00ee62deb85f5725b07f7b0616b769608d [file] [log] [blame]
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/renderer/push_messaging/push_provider.h"
#include <memory>
#include <utility>
#include "base/bind_helpers.h"
#include "base/lazy_instance.h"
#include "base/threading/thread_local.h"
#include "content/child/child_thread_impl.h"
#include "content/public/common/child_process_host.h"
#include "content/public/common/push_messaging_status.mojom.h"
#include "content/public/common/push_subscription_options.h"
#include "content/public/common/service_names.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
#include "third_party/blink/public/platform/modules/push_messaging/web_push_subscription.h"
#include "third_party/blink/public/platform/modules/push_messaging/web_push_subscription_options.h"
#include "third_party/blink/public/platform/web_string.h"
namespace content {
namespace {
const char* PushRegistrationStatusToString(
mojom::PushRegistrationStatus status) {
switch (status) {
case mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE:
case mojom::PushRegistrationStatus::
SUCCESS_NEW_SUBSCRIPTION_FROM_PUSH_SERVICE:
return "Registration successful - from push service";
case mojom::PushRegistrationStatus::NO_SERVICE_WORKER:
return "Registration failed - no Service Worker";
case mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE:
return "Registration failed - push service not available";
case mojom::PushRegistrationStatus::LIMIT_REACHED:
return "Registration failed - registration limit has been reached";
case mojom::PushRegistrationStatus::PERMISSION_DENIED:
return "Registration failed - permission denied";
case mojom::PushRegistrationStatus::SERVICE_ERROR:
return "Registration failed - push service error";
case mojom::PushRegistrationStatus::NO_SENDER_ID:
return "Registration failed - missing applicationServerKey, and "
"gcm_sender_id not found in manifest";
case mojom::PushRegistrationStatus::STORAGE_ERROR:
return "Registration failed - storage error";
case mojom::PushRegistrationStatus::SUCCESS_FROM_CACHE:
return "Registration successful - from cache";
case mojom::PushRegistrationStatus::NETWORK_ERROR:
return "Registration failed - could not connect to push server";
case mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED:
// We split this out for UMA, but it must be indistinguishable to JS.
return PushRegistrationStatusToString(
mojom::PushRegistrationStatus::PERMISSION_DENIED);
case mojom::PushRegistrationStatus::PUBLIC_KEY_UNAVAILABLE:
return "Registration failed - could not retrieve the public key";
case mojom::PushRegistrationStatus::MANIFEST_EMPTY_OR_MISSING:
return "Registration failed - missing applicationServerKey, and manifest "
"empty or missing";
case mojom::PushRegistrationStatus::SENDER_ID_MISMATCH:
return "Registration failed - A subscription with a different "
"applicationServerKey (or gcm_sender_id) already exists; to "
"change the applicationServerKey, unsubscribe then resubscribe.";
case mojom::PushRegistrationStatus::STORAGE_CORRUPT:
return "Registration failed - storage corrupt";
case mojom::PushRegistrationStatus::RENDERER_SHUTDOWN:
return "Registration failed - renderer shutdown";
}
NOTREACHED();
return "";
}
} // namespace
blink::WebPushError PushRegistrationStatusToWebPushError(
mojom::PushRegistrationStatus status) {
blink::WebPushError::ErrorType error_type =
blink::WebPushError::kErrorTypeAbort;
switch (status) {
case mojom::PushRegistrationStatus::PERMISSION_DENIED:
error_type = blink::WebPushError::kErrorTypeNotAllowed;
break;
case mojom::PushRegistrationStatus::SENDER_ID_MISMATCH:
error_type = blink::WebPushError::kErrorTypeInvalidState;
break;
case mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE:
case mojom::PushRegistrationStatus::
SUCCESS_NEW_SUBSCRIPTION_FROM_PUSH_SERVICE:
case mojom::PushRegistrationStatus::NO_SERVICE_WORKER:
case mojom::PushRegistrationStatus::SERVICE_NOT_AVAILABLE:
case mojom::PushRegistrationStatus::LIMIT_REACHED:
case mojom::PushRegistrationStatus::SERVICE_ERROR:
case mojom::PushRegistrationStatus::NO_SENDER_ID:
case mojom::PushRegistrationStatus::STORAGE_ERROR:
case mojom::PushRegistrationStatus::SUCCESS_FROM_CACHE:
case mojom::PushRegistrationStatus::NETWORK_ERROR:
case mojom::PushRegistrationStatus::INCOGNITO_PERMISSION_DENIED:
case mojom::PushRegistrationStatus::PUBLIC_KEY_UNAVAILABLE:
case mojom::PushRegistrationStatus::MANIFEST_EMPTY_OR_MISSING:
case mojom::PushRegistrationStatus::STORAGE_CORRUPT:
case mojom::PushRegistrationStatus::RENDERER_SHUTDOWN:
error_type = blink::WebPushError::kErrorTypeAbort;
break;
}
return blink::WebPushError(
error_type,
blink::WebString::FromUTF8(PushRegistrationStatusToString(status)));
}
static base::LazyInstance<base::ThreadLocalPointer<PushProvider>>::Leaky
g_push_provider_tls = LAZY_INSTANCE_INITIALIZER;
PushProvider::PushProvider(const scoped_refptr<base::SingleThreadTaskRunner>&
main_thread_task_runner) {
DCHECK(main_thread_task_runner);
auto request = mojo::MakeRequest(&push_messaging_manager_);
if (!main_thread_task_runner->BelongsToCurrentThread()) {
main_thread_task_runner->PostTask(
FROM_HERE,
base::BindOnce(&PushProvider::GetInterface, std::move(request)));
} else {
GetInterface(std::move(request));
}
g_push_provider_tls.Pointer()->Set(this);
}
PushProvider::~PushProvider() {
g_push_provider_tls.Pointer()->Set(nullptr);
}
PushProvider* PushProvider::ThreadSpecificInstance(
const scoped_refptr<base::SingleThreadTaskRunner>&
main_thread_task_runner) {
if (g_push_provider_tls.Pointer()->Get())
return g_push_provider_tls.Pointer()->Get();
PushProvider* provider = new PushProvider(main_thread_task_runner);
if (WorkerThread::GetCurrentId())
WorkerThread::AddObserver(provider);
return provider;
}
// static
void PushProvider::GetInterface(mojom::PushMessagingRequest request) {
if (ChildThreadImpl::current()) {
ChildThreadImpl::current()->GetConnector()->BindInterface(
mojom::kBrowserServiceName, std::move(request));
}
}
void PushProvider::WillStopCurrentWorkerThread() {
delete this;
}
void PushProvider::Subscribe(
int64_t service_worker_registration_id,
const blink::WebPushSubscriptionOptions& options,
bool user_gesture,
std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks) {
DCHECK(callbacks);
PushSubscriptionOptions content_options;
content_options.user_visible_only = options.user_visible_only;
// Just treat the server key as a string of bytes and pass it to the push
// service.
content_options.sender_info = options.application_server_key.Latin1();
push_messaging_manager_->Subscribe(
ChildProcessHost::kInvalidUniqueID, service_worker_registration_id,
content_options, user_gesture,
// Safe to use base::Unretained because |push_messaging_manager_ |is owned
// by |this|.
base::BindOnce(&PushProvider::DidSubscribe, base::Unretained(this),
std::move(callbacks)));
}
void PushProvider::DidSubscribe(
std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks,
mojom::PushRegistrationStatus status,
const base::Optional<GURL>& endpoint,
const base::Optional<PushSubscriptionOptions>& options,
const base::Optional<std::vector<uint8_t>>& p256dh,
const base::Optional<std::vector<uint8_t>>& auth) {
DCHECK(callbacks);
if (status == mojom::PushRegistrationStatus::SUCCESS_FROM_PUSH_SERVICE ||
status == mojom::PushRegistrationStatus::
SUCCESS_NEW_SUBSCRIPTION_FROM_PUSH_SERVICE ||
status == mojom::PushRegistrationStatus::SUCCESS_FROM_CACHE) {
DCHECK(endpoint);
DCHECK(options);
DCHECK(p256dh);
DCHECK(auth);
callbacks->OnSuccess(std::make_unique<blink::WebPushSubscription>(
endpoint.value(), options.value().user_visible_only,
blink::WebString::FromLatin1(options.value().sender_info),
p256dh.value(), auth.value()));
} else {
callbacks->OnError(PushRegistrationStatusToWebPushError(status));
}
}
void PushProvider::Unsubscribe(
int64_t service_worker_registration_id,
std::unique_ptr<blink::WebPushUnsubscribeCallbacks> callbacks) {
DCHECK(callbacks);
push_messaging_manager_->Unsubscribe(
service_worker_registration_id,
// Safe to use base::Unretained because |push_messaging_manager_ |is owned
// by |this|.
base::BindOnce(&PushProvider::DidUnsubscribe, base::Unretained(this),
std::move(callbacks)));
}
void PushProvider::DidUnsubscribe(
std::unique_ptr<blink::WebPushUnsubscribeCallbacks> callbacks,
blink::WebPushError::ErrorType error_type,
bool did_unsubscribe,
const base::Optional<std::string>& error_message) {
DCHECK(callbacks);
// ErrorTypeNone indicates success.
if (error_type == blink::WebPushError::kErrorTypeNone) {
callbacks->OnSuccess(did_unsubscribe);
} else {
callbacks->OnError(blink::WebPushError(
error_type, blink::WebString::FromUTF8(error_message->c_str())));
}
}
void PushProvider::GetSubscription(
int64_t service_worker_registration_id,
std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks) {
DCHECK(callbacks);
push_messaging_manager_->GetSubscription(
service_worker_registration_id,
// Safe to use base::Unretained because |push_messaging_manager_ |is owned
// by |this|.
base::BindOnce(&PushProvider::DidGetSubscription, base::Unretained(this),
std::move(callbacks)));
}
void PushProvider::DidGetSubscription(
std::unique_ptr<blink::WebPushSubscriptionCallbacks> callbacks,
mojom::PushGetRegistrationStatus status,
const base::Optional<GURL>& endpoint,
const base::Optional<PushSubscriptionOptions>& options,
const base::Optional<std::vector<uint8_t>>& p256dh,
const base::Optional<std::vector<uint8_t>>& auth) {
DCHECK(callbacks);
if (status == mojom::PushGetRegistrationStatus::SUCCESS) {
DCHECK(endpoint);
DCHECK(options);
DCHECK(p256dh);
DCHECK(auth);
callbacks->OnSuccess(std::make_unique<blink::WebPushSubscription>(
endpoint.value(), options.value().user_visible_only,
blink::WebString::FromLatin1(options.value().sender_info),
p256dh.value(), auth.value()));
} else {
// We are only expecting an error if we can't find a registration.
callbacks->OnSuccess(nullptr);
}
}
} // namespace content