| // 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 |