blob: 6ec8a300bf2aa04afa21a3bed5e7d7d7db43f739 [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/browser/push_messaging/push_messaging_router.h"
#include <string>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "content/browser/devtools/devtools_background_services_context_impl.h"
#include "content/browser/service_worker/service_worker_context_wrapper.h"
#include "content/browser/service_worker/service_worker_registration.h"
#include "content/browser/service_worker/service_worker_storage.h"
#include "content/browser/storage_partition_impl.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/common/content_features.h"
#include "third_party/blink/public/common/service_worker/service_worker_status_code.h"
#include "third_party/blink/public/mojom/push_messaging/push_messaging_status.mojom.h"
namespace content {
namespace {
using ServiceWorkerStartCallback = base::OnceCallback<void(
scoped_refptr<ServiceWorkerVersion>,
scoped_refptr<DevToolsBackgroundServicesContextImpl>,
blink::ServiceWorkerStatusCode)>;
void RunPushEventCallback(
PushMessagingRouter::PushEventCallback deliver_message_callback,
blink::mojom::PushEventStatus push_event_status) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
// Use PostTask() instead of RunOrPostTaskOnThread() to ensure the callback
// is called asynchronously.
GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce(std::move(deliver_message_callback), push_event_status));
}
// Given the |service_worker_registration|, this method finds and finishes the
// |callback| by finding the |service_worker_version|. Must be called on the
// ServiceWorkerContext core thread.
void DidFindServiceWorkerRegistration(
ServiceWorkerMetrics::EventType event_type,
scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context,
ServiceWorkerStartCallback callback,
blink::ServiceWorkerStatusCode service_worker_status,
scoped_refptr<ServiceWorkerRegistration> service_worker_registration) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
if (event_type == ServiceWorkerMetrics::EventType::PUSH) {
UMA_HISTOGRAM_ENUMERATION("PushMessaging.DeliveryStatus.FindServiceWorker",
service_worker_status);
}
if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) {
std::move(callback).Run(nullptr /* service_worker_version */,
nullptr /* devtools_context */,
service_worker_status);
return;
}
ServiceWorkerVersion* version = service_worker_registration->active_version();
DCHECK(version);
version->RunAfterStartWorker(
event_type,
base::BindOnce(std::move(callback), base::WrapRefCounted(version),
std::move(devtools_context)));
}
// Finds the |service_worker_registration|. Must be called on the
// ServiceWorkerContext core thread.
void FindServiceWorkerRegistration(
ServiceWorkerMetrics::EventType event_type,
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context,
scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context,
const url::Origin& origin,
int64_t service_worker_registration_id,
ServiceWorkerStartCallback callback) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
// Try to acquire the registration from storage. If it's already live we'll
// receive it right away. If not, it will be revived from storage.
service_worker_context->FindReadyRegistrationForId(
service_worker_registration_id, origin,
base::BindOnce(&DidFindServiceWorkerRegistration, event_type,
std::move(devtools_context), std::move(callback)));
}
// According to the |event_type| this method will start finding the
// |service_worker_version| and |devtools_context| for the event. Must be called
// on the UI thread.
void StartServiceWorkerForDispatch(ServiceWorkerMetrics::EventType event_type,
BrowserContext* browser_context,
const GURL& origin,
int64_t service_worker_registration_id,
ServiceWorkerStartCallback callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
StoragePartition* partition =
BrowserContext::GetStoragePartitionForSite(browser_context, origin);
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context =
static_cast<ServiceWorkerContextWrapper*>(
partition->GetServiceWorkerContext());
auto devtools_context =
base::WrapRefCounted<DevToolsBackgroundServicesContextImpl>(
service_worker_context->storage_partition()
->GetDevToolsBackgroundServicesContext());
RunOrPostTaskOnThread(
FROM_HERE, ServiceWorkerContext::GetCoreThreadId(),
base::BindOnce(&FindServiceWorkerRegistration, event_type,
std::move(service_worker_context),
std::move(devtools_context), url::Origin::Create(origin),
service_worker_registration_id, std::move(callback)));
}
} // namespace
// static
void PushMessagingRouter::DeliverMessage(
BrowserContext* browser_context,
const GURL& origin,
int64_t service_worker_registration_id,
const std::string& message_id,
base::Optional<std::string> payload,
PushEventCallback deliver_message_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
StartServiceWorkerForDispatch(
ServiceWorkerMetrics::EventType::PUSH, browser_context, origin,
service_worker_registration_id,
base::BindOnce(&PushMessagingRouter::DeliverMessageToWorker, message_id,
std::move(payload), std::move(deliver_message_callback)));
}
// static
void PushMessagingRouter::DeliverMessageToWorker(
const std::string& message_id,
base::Optional<std::string> payload,
PushEventCallback deliver_message_callback,
scoped_refptr<ServiceWorkerVersion> service_worker,
scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context,
blink::ServiceWorkerStatusCode status) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
// Service worker registration was not found, run callback immediately
if (!service_worker) {
DCHECK_NE(blink::ServiceWorkerStatusCode::kOk, status);
RunPushEventCallback(
std::move(deliver_message_callback),
status == blink::ServiceWorkerStatusCode::kErrorNotFound
? blink::mojom::PushEventStatus::NO_SERVICE_WORKER
: blink::mojom::PushEventStatus::SERVICE_WORKER_ERROR);
return;
}
// RunAfterStartWorker was not successful, end message delivery and log error
// in devtools_context before running RunPushEventCallback
if (status != blink::ServiceWorkerStatusCode::kOk) {
DeliverMessageEnd(std::move(service_worker), std::move(devtools_context),
message_id, std::move(deliver_message_callback), status);
return;
}
int request_id = service_worker->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::PUSH,
base::BindOnce(&PushMessagingRouter::DeliverMessageEnd, service_worker,
devtools_context, message_id,
std::move(deliver_message_callback)),
base::TimeDelta::FromSeconds(blink::mojom::kPushEventTimeoutSeconds),
ServiceWorkerVersion::KILL_ON_TIMEOUT);
service_worker->endpoint()->DispatchPushEvent(
payload, service_worker->CreateSimpleEventCallback(request_id));
if (devtools_context->IsRecording(
DevToolsBackgroundService::kPushMessaging)) {
std::map<std::string, std::string> event_metadata;
if (payload)
event_metadata["Payload"] = *payload;
devtools_context->LogBackgroundServiceEventOnCoreThread(
service_worker->registration_id(), service_worker->origin(),
DevToolsBackgroundService::kPushMessaging, "Push event dispatched",
message_id, event_metadata);
}
}
// static
void PushMessagingRouter::DeliverMessageEnd(
scoped_refptr<ServiceWorkerVersion> service_worker,
scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context,
const std::string& message_id,
PushEventCallback deliver_message_callback,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
UMA_HISTOGRAM_ENUMERATION("PushMessaging.DeliveryStatus.ServiceWorkerEvent",
service_worker_status);
blink::mojom::PushEventStatus push_event_status =
blink::mojom::PushEventStatus::SERVICE_WORKER_ERROR;
std::string status_description;
switch (service_worker_status) {
case blink::ServiceWorkerStatusCode::kOk:
push_event_status = blink::mojom::PushEventStatus::SUCCESS;
status_description = "Success";
break;
case blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected:
push_event_status =
blink::mojom::PushEventStatus::EVENT_WAITUNTIL_REJECTED;
status_description = "waitUntil Rejected";
break;
case blink::ServiceWorkerStatusCode::kErrorTimeout:
push_event_status = blink::mojom::PushEventStatus::TIMEOUT;
status_description = "Timeout";
break;
case blink::ServiceWorkerStatusCode::kErrorFailed:
case blink::ServiceWorkerStatusCode::kErrorAbort:
case blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed:
case blink::ServiceWorkerStatusCode::kErrorProcessNotFound:
case blink::ServiceWorkerStatusCode::kErrorNotFound:
case blink::ServiceWorkerStatusCode::kErrorIpcFailed:
case blink::ServiceWorkerStatusCode::kErrorScriptEvaluateFailed:
case blink::ServiceWorkerStatusCode::kErrorDiskCache:
case blink::ServiceWorkerStatusCode::kErrorRedundant:
case blink::ServiceWorkerStatusCode::kErrorDisallowed:
push_event_status = blink::mojom::PushEventStatus::SERVICE_WORKER_ERROR;
break;
case blink::ServiceWorkerStatusCode::kErrorExists:
case blink::ServiceWorkerStatusCode::kErrorInstallWorkerFailed:
case blink::ServiceWorkerStatusCode::kErrorActivateWorkerFailed:
case blink::ServiceWorkerStatusCode::kErrorNetwork:
case blink::ServiceWorkerStatusCode::kErrorSecurity:
case blink::ServiceWorkerStatusCode::kErrorState:
case blink::ServiceWorkerStatusCode::kErrorInvalidArguments:
case blink::ServiceWorkerStatusCode::kErrorStorageDisconnected:
NOTREACHED() << "Got unexpected error code: "
<< static_cast<uint32_t>(service_worker_status) << " "
<< blink::ServiceWorkerStatusToString(service_worker_status);
push_event_status = blink::mojom::PushEventStatus::SERVICE_WORKER_ERROR;
break;
}
RunPushEventCallback(std::move(deliver_message_callback), push_event_status);
if (devtools_context->IsRecording(
DevToolsBackgroundService::kPushMessaging) &&
push_event_status !=
blink::mojom::PushEventStatus::SERVICE_WORKER_ERROR) {
devtools_context->LogBackgroundServiceEventOnCoreThread(
service_worker->registration_id(), service_worker->origin(),
DevToolsBackgroundService::kPushMessaging, "Push event completed",
message_id, {{"Status", status_description}});
}
}
// static
void PushMessagingRouter::FireSubscriptionChangeEvent(
BrowserContext* browser_context,
const GURL& origin,
int64_t service_worker_registration_id,
blink::mojom::PushSubscriptionPtr new_subscription,
blink::mojom::PushSubscriptionPtr old_subscription,
PushEventCallback subscription_change_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(base::FeatureList::IsEnabled(features::kPushSubscriptionChangeEvent));
StartServiceWorkerForDispatch(
ServiceWorkerMetrics::EventType::PUSH_SUBSCRIPTION_CHANGE,
browser_context, origin, service_worker_registration_id,
base::BindOnce(&PushMessagingRouter::FireSubscriptionChangeEventToWorker,
std::move(new_subscription), std::move(old_subscription),
std::move(subscription_change_callback)));
}
// static
void PushMessagingRouter::FireSubscriptionChangeEventToWorker(
blink::mojom::PushSubscriptionPtr new_subscription,
blink::mojom::PushSubscriptionPtr old_subscription,
PushEventCallback subscription_change_callback,
scoped_refptr<ServiceWorkerVersion> service_worker,
scoped_refptr<DevToolsBackgroundServicesContextImpl> devtools_context,
blink::ServiceWorkerStatusCode status) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
DCHECK(base::FeatureList::IsEnabled(features::kPushSubscriptionChangeEvent));
if (!service_worker) {
DCHECK_NE(blink::ServiceWorkerStatusCode::kOk, status);
RunPushEventCallback(
std::move(subscription_change_callback),
status == blink::ServiceWorkerStatusCode::kErrorNotFound
? blink::mojom::PushEventStatus::NO_SERVICE_WORKER
: blink::mojom::PushEventStatus::SERVICE_WORKER_ERROR);
return;
}
if (status != blink::ServiceWorkerStatusCode::kOk) {
FireSubscriptionChangeEventEnd(std::move(service_worker),
std::move(subscription_change_callback),
status);
return;
}
int request_id = service_worker->StartRequestWithCustomTimeout(
ServiceWorkerMetrics::EventType::PUSH_SUBSCRIPTION_CHANGE,
base::BindOnce(&PushMessagingRouter::FireSubscriptionChangeEventEnd,
service_worker, std::move(subscription_change_callback)),
base::TimeDelta::FromSeconds(blink::mojom::kPushEventTimeoutSeconds),
ServiceWorkerVersion::KILL_ON_TIMEOUT);
service_worker->endpoint()->DispatchPushSubscriptionChangeEvent(
std::move(old_subscription), std::move(new_subscription),
service_worker->CreateSimpleEventCallback(request_id));
}
// static
void PushMessagingRouter::FireSubscriptionChangeEventEnd(
scoped_refptr<ServiceWorkerVersion> service_worker,
PushEventCallback subscription_change_callback,
blink::ServiceWorkerStatusCode service_worker_status) {
DCHECK_CURRENTLY_ON(ServiceWorkerContext::GetCoreThreadId());
blink::mojom::PushEventStatus push_event_status =
blink::mojom::PushEventStatus::SERVICE_WORKER_ERROR;
switch (service_worker_status) {
case blink::ServiceWorkerStatusCode::kOk:
push_event_status = blink::mojom::PushEventStatus::SUCCESS;
break;
case blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected:
push_event_status =
blink::mojom::PushEventStatus::EVENT_WAITUNTIL_REJECTED;
break;
case blink::ServiceWorkerStatusCode::kErrorTimeout:
push_event_status = blink::mojom::PushEventStatus::TIMEOUT;
break;
default:
break;
}
RunPushEventCallback(std::move(subscription_change_callback),
push_event_status);
}
} // namespace content