blob: 5f097bc0251c56efd78460d4240f2c873e1a0016 [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/notifications/notification_event_dispatcher_impl.h"
#include "base/callback.h"
#include "build/build_config.h"
#include "content/browser/notifications/platform_notification_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/common/service_worker/service_worker_messages.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/platform_notification_data.h"
namespace content {
namespace {
using NotificationDispatchCompleteCallback =
NotificationEventDispatcher::NotificationDispatchCompleteCallback;
using NotificationOperationCallback =
base::Callback<void(const ServiceWorkerRegistration*,
const NotificationDatabaseData&)>;
using NotificationOperationCallbackWithContext =
base::Callback<void(const scoped_refptr<PlatformNotificationContext>&,
const ServiceWorkerRegistration*,
const NotificationDatabaseData&)>;
// To be called when a notification event has finished executing. Will post a
// task to call |dispatch_complete_callback| on the UI thread.
void NotificationEventFinished(
const NotificationDispatchCompleteCallback& dispatch_complete_callback,
PersistentNotificationStatus status) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(dispatch_complete_callback, status));
}
// To be called when a notification event has finished with a
// ServiceWorkerStatusCode result. Will call NotificationEventFinished with a
// PersistentNotificationStatus derived from the service worker status.
void ServiceWorkerNotificationEventFinished(
const NotificationDispatchCompleteCallback& dispatch_complete_callback,
ServiceWorkerStatusCode service_worker_status) {
#if defined(OS_ANDROID)
// This LOG(INFO) deliberately exists to help track down the cause of
// https://crbug.com/534537, where notifications sometimes do not react to
// the user clicking on them. It should be removed once that's fixed.
LOG(INFO) << "The notification event has finished: " << service_worker_status;
#endif
PersistentNotificationStatus status = PERSISTENT_NOTIFICATION_STATUS_SUCCESS;
switch (service_worker_status) {
case SERVICE_WORKER_OK:
// Success status was initialized above.
break;
case SERVICE_WORKER_ERROR_EVENT_WAITUNTIL_REJECTED:
status = PERSISTENT_NOTIFICATION_STATUS_EVENT_WAITUNTIL_REJECTED;
break;
case SERVICE_WORKER_ERROR_FAILED:
case SERVICE_WORKER_ERROR_ABORT:
case SERVICE_WORKER_ERROR_START_WORKER_FAILED:
case SERVICE_WORKER_ERROR_PROCESS_NOT_FOUND:
case SERVICE_WORKER_ERROR_NOT_FOUND:
case SERVICE_WORKER_ERROR_EXISTS:
case SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED:
case SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED:
case SERVICE_WORKER_ERROR_IPC_FAILED:
case SERVICE_WORKER_ERROR_NETWORK:
case SERVICE_WORKER_ERROR_SECURITY:
case SERVICE_WORKER_ERROR_STATE:
case SERVICE_WORKER_ERROR_TIMEOUT:
case SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED:
case SERVICE_WORKER_ERROR_DISK_CACHE:
case SERVICE_WORKER_ERROR_REDUNDANT:
case SERVICE_WORKER_ERROR_DISALLOWED:
case SERVICE_WORKER_ERROR_MAX_VALUE:
status = PERSISTENT_NOTIFICATION_STATUS_SERVICE_WORKER_ERROR;
break;
}
NotificationEventFinished(dispatch_complete_callback, status);
}
// Dispatches the given notification action event on
// |service_worker_registration| if the registration was available. Must be
// called on the IO thread.
void DispatchNotificationEventOnRegistration(
const NotificationDatabaseData& notification_database_data,
const scoped_refptr<PlatformNotificationContext>& notification_context,
const NotificationOperationCallback& dispatch_event_action,
const NotificationDispatchCompleteCallback& dispatch_error_callback,
ServiceWorkerStatusCode service_worker_status,
scoped_refptr<ServiceWorkerRegistration> service_worker_registration) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
#if defined(OS_ANDROID)
// This LOG(INFO) deliberately exists to help track down the cause of
// https://crbug.com/534537, where notifications sometimes do not react to
// the user clicking on them. It should be removed once that's fixed.
LOG(INFO) << "Trying to dispatch notification for SW with status: "
<< service_worker_status;
#endif
if (service_worker_status == SERVICE_WORKER_OK) {
DCHECK(service_worker_registration->active_version());
dispatch_event_action.Run(service_worker_registration.get(),
notification_database_data);
return;
}
PersistentNotificationStatus status = PERSISTENT_NOTIFICATION_STATUS_SUCCESS;
switch (service_worker_status) {
case SERVICE_WORKER_ERROR_NOT_FOUND:
status = PERSISTENT_NOTIFICATION_STATUS_NO_SERVICE_WORKER;
break;
case SERVICE_WORKER_ERROR_FAILED:
case SERVICE_WORKER_ERROR_ABORT:
case SERVICE_WORKER_ERROR_START_WORKER_FAILED:
case SERVICE_WORKER_ERROR_PROCESS_NOT_FOUND:
case SERVICE_WORKER_ERROR_EXISTS:
case SERVICE_WORKER_ERROR_INSTALL_WORKER_FAILED:
case SERVICE_WORKER_ERROR_ACTIVATE_WORKER_FAILED:
case SERVICE_WORKER_ERROR_IPC_FAILED:
case SERVICE_WORKER_ERROR_NETWORK:
case SERVICE_WORKER_ERROR_SECURITY:
case SERVICE_WORKER_ERROR_EVENT_WAITUNTIL_REJECTED:
case SERVICE_WORKER_ERROR_STATE:
case SERVICE_WORKER_ERROR_TIMEOUT:
case SERVICE_WORKER_ERROR_SCRIPT_EVALUATE_FAILED:
case SERVICE_WORKER_ERROR_DISK_CACHE:
case SERVICE_WORKER_ERROR_REDUNDANT:
case SERVICE_WORKER_ERROR_DISALLOWED:
case SERVICE_WORKER_ERROR_MAX_VALUE:
status = PERSISTENT_NOTIFICATION_STATUS_SERVICE_WORKER_ERROR;
break;
case SERVICE_WORKER_OK:
NOTREACHED();
break;
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE,
base::Bind(dispatch_error_callback, status));
}
// Finds the ServiceWorkerRegistration associated with the |origin| and
// |service_worker_registration_id|. Must be called on the IO thread.
void FindServiceWorkerRegistration(
const GURL& origin,
const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context,
const scoped_refptr<PlatformNotificationContext>& notification_context,
const NotificationOperationCallback& notification_action_callback,
const NotificationDispatchCompleteCallback& dispatch_error_callback,
bool success,
const NotificationDatabaseData& notification_database_data) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
#if defined(OS_ANDROID)
// This LOG(INFO) deliberately exists to help track down the cause of
// https://crbug.com/534537, where notifications sometimes do not react to
// the user clicking on them. It should be removed once that's fixed.
LOG(INFO) << "Lookup for ServiceWoker Registration: success: " << success;
#endif
if (!success) {
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(dispatch_error_callback,
PERSISTENT_NOTIFICATION_STATUS_DATABASE_ERROR));
return;
}
service_worker_context->FindReadyRegistrationForId(
notification_database_data.service_worker_registration_id, origin,
base::Bind(&DispatchNotificationEventOnRegistration,
notification_database_data, notification_context,
notification_action_callback, dispatch_error_callback));
}
// Reads the data associated with the |notification_id| belonging to |origin|
// from the notification context.
void ReadNotificationDatabaseData(
const std::string& notification_id,
const GURL& origin,
const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context,
const scoped_refptr<PlatformNotificationContext>& notification_context,
const NotificationOperationCallback& notification_read_callback,
const NotificationDispatchCompleteCallback& dispatch_error_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
notification_context->ReadNotificationData(
notification_id, origin,
base::Bind(&FindServiceWorkerRegistration, origin, service_worker_context,
notification_context, notification_read_callback,
dispatch_error_callback));
}
// -----------------------------------------------------------------------------
// Dispatches the notificationclick event on |service_worker|. Must be called on
// the IO thread, and with the worker running.
void DispatchNotificationClickEventOnWorker(
const scoped_refptr<ServiceWorkerVersion>& service_worker,
const NotificationDatabaseData& notification_database_data,
int action_index,
const base::NullableString16& reply,
const ServiceWorkerVersion::StatusCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
int request_id = service_worker->StartRequest(
ServiceWorkerMetrics::EventType::NOTIFICATION_CLICK, callback);
service_worker->DispatchSimpleEvent<
ServiceWorkerHostMsg_NotificationClickEventFinished>(
request_id,
ServiceWorkerMsg_NotificationClickEvent(
request_id, notification_database_data.notification_id,
notification_database_data.notification_data, action_index, reply));
}
// Dispatches the notification click event on the |service_worker_registration|.
void DoDispatchNotificationClickEvent(
int action_index,
const base::NullableString16& reply,
const NotificationDispatchCompleteCallback& dispatch_complete_callback,
const scoped_refptr<PlatformNotificationContext>& notification_context,
const ServiceWorkerRegistration* service_worker_registration,
const NotificationDatabaseData& notification_database_data) {
ServiceWorkerVersion::StatusCallback status_callback = base::Bind(
&ServiceWorkerNotificationEventFinished, dispatch_complete_callback);
service_worker_registration->active_version()->RunAfterStartWorker(
ServiceWorkerMetrics::EventType::NOTIFICATION_CLICK,
base::Bind(
&DispatchNotificationClickEventOnWorker,
make_scoped_refptr(service_worker_registration->active_version()),
notification_database_data, action_index, reply, status_callback),
status_callback);
}
// -----------------------------------------------------------------------------
// Called when the notification data has been deleted to finish the notification
// close event.
void OnPersistentNotificationDataDeleted(
ServiceWorkerStatusCode service_worker_status,
const NotificationDispatchCompleteCallback& dispatch_complete_callback,
bool success) {
if (service_worker_status != SERVICE_WORKER_OK) {
ServiceWorkerNotificationEventFinished(dispatch_complete_callback,
service_worker_status);
return;
}
NotificationEventFinished(
dispatch_complete_callback,
success ? PERSISTENT_NOTIFICATION_STATUS_SUCCESS
: PERSISTENT_NOTIFICATION_STATUS_DATABASE_ERROR);
}
// Called when the persistent notification close event has been handled
// to remove the notification from the database.
void DeleteNotificationDataFromDatabase(
const std::string& notification_id,
const GURL& origin,
const scoped_refptr<PlatformNotificationContext>& notification_context,
const NotificationDispatchCompleteCallback& dispatch_complete_callback,
ServiceWorkerStatusCode status_code) {
notification_context->DeleteNotificationData(
notification_id, origin,
base::Bind(&OnPersistentNotificationDataDeleted, status_code,
dispatch_complete_callback));
}
// Dispatches the notificationclose event on |service_worker|. Must be called on
// the IO thread, and with the worker running.
void DispatchNotificationCloseEventOnWorker(
const scoped_refptr<ServiceWorkerVersion>& service_worker,
const NotificationDatabaseData& notification_database_data,
const ServiceWorkerVersion::StatusCallback& callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
int request_id = service_worker->StartRequest(
ServiceWorkerMetrics::EventType::NOTIFICATION_CLOSE, callback);
service_worker->DispatchSimpleEvent<
ServiceWorkerHostMsg_NotificationCloseEventFinished>(
request_id, ServiceWorkerMsg_NotificationCloseEvent(
request_id, notification_database_data.notification_id,
notification_database_data.notification_data));
}
// Actually dispatches the notification close event on the service worker
// registration.
void DoDispatchNotificationCloseEvent(
const std::string& notification_id,
bool by_user,
const NotificationDispatchCompleteCallback& dispatch_complete_callback,
const scoped_refptr<PlatformNotificationContext>& notification_context,
const ServiceWorkerRegistration* service_worker_registration,
const NotificationDatabaseData& notification_database_data) {
const ServiceWorkerVersion::StatusCallback dispatch_event_callback =
base::Bind(&DeleteNotificationDataFromDatabase, notification_id,
notification_database_data.origin, notification_context,
dispatch_complete_callback);
if (by_user) {
service_worker_registration->active_version()->RunAfterStartWorker(
ServiceWorkerMetrics::EventType::NOTIFICATION_CLOSE,
base::Bind(
&DispatchNotificationCloseEventOnWorker,
make_scoped_refptr(service_worker_registration->active_version()),
notification_database_data, dispatch_event_callback),
dispatch_event_callback);
} else {
dispatch_event_callback.Run(ServiceWorkerStatusCode::SERVICE_WORKER_OK);
}
}
// Dispatches any notification event. The actual, specific event dispatch should
// be done by the |notification_action_callback|.
void DispatchNotificationEvent(
BrowserContext* browser_context,
const std::string& notification_id,
const GURL& origin,
const NotificationOperationCallbackWithContext&
notification_action_callback,
const NotificationDispatchCompleteCallback& notification_error_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(!notification_id.empty());
DCHECK(origin.is_valid());
StoragePartition* partition =
BrowserContext::GetStoragePartitionForSite(browser_context, origin);
scoped_refptr<ServiceWorkerContextWrapper> service_worker_context =
static_cast<ServiceWorkerContextWrapper*>(
partition->GetServiceWorkerContext());
scoped_refptr<PlatformNotificationContext> notification_context =
partition->GetPlatformNotificationContext();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&ReadNotificationDatabaseData, notification_id, origin,
service_worker_context, notification_context,
base::Bind(notification_action_callback, notification_context),
notification_error_callback));
}
} // namespace
// static
NotificationEventDispatcher* NotificationEventDispatcher::GetInstance() {
return NotificationEventDispatcherImpl::GetInstance();
}
NotificationEventDispatcherImpl*
NotificationEventDispatcherImpl::GetInstance() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
return base::Singleton<NotificationEventDispatcherImpl>::get();
}
NotificationEventDispatcherImpl::NotificationEventDispatcherImpl() {}
NotificationEventDispatcherImpl::~NotificationEventDispatcherImpl() {}
void NotificationEventDispatcherImpl::DispatchNotificationClickEvent(
BrowserContext* browser_context,
const std::string& notification_id,
const GURL& origin,
int action_index,
const base::NullableString16& reply,
const NotificationDispatchCompleteCallback& dispatch_complete_callback) {
DispatchNotificationEvent(
browser_context, notification_id, origin,
base::Bind(&DoDispatchNotificationClickEvent, action_index, reply,
dispatch_complete_callback),
dispatch_complete_callback);
}
void NotificationEventDispatcherImpl::DispatchNotificationCloseEvent(
BrowserContext* browser_context,
const std::string& notification_id,
const GURL& origin,
bool by_user,
const NotificationDispatchCompleteCallback& dispatch_complete_callback) {
DispatchNotificationEvent(
browser_context, notification_id, origin,
base::Bind(&DoDispatchNotificationCloseEvent, notification_id, by_user,
dispatch_complete_callback),
dispatch_complete_callback);
}
} // namespace content