| // 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/notifications/notification_event_dispatcher_impl.h" |
| |
| #include <optional> |
| |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/functional/callback_helpers.h" |
| #include "base/notreached.h" |
| #include "build/build_config.h" |
| #include "content/browser/notifications/devtools_event_logging.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/public/browser/browser_context.h" |
| #include "content/public/browser/browser_task_traits.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/storage_partition.h" |
| #include "content/public/browser/weak_document_ptr.h" |
| #include "content/public/common/persistent_notification_status.h" |
| #include "third_party/blink/public/common/notifications/platform_notification_data.h" |
| #include "third_party/blink/public/common/storage_key/storage_key.h" |
| |
| namespace content { |
| namespace { |
| |
| using NotificationDispatchCompleteCallback = |
| base::OnceCallback<void(PersistentNotificationStatus, |
| blink::ServiceWorkerStatusCode)>; |
| using PersistentNotificationDispatchCompleteCallback = |
| base::OnceCallback<void(PersistentNotificationStatus)>; |
| using NotificationOperationCallback = |
| base::OnceCallback<void(const ServiceWorkerRegistration*, |
| const NotificationDatabaseData&, |
| NotificationDispatchCompleteCallback)>; |
| using NotificationOperationCallbackWithContext = |
| base::OnceCallback<void(const scoped_refptr<PlatformNotificationContext>&, |
| BrowserContext* browser_context, |
| const ServiceWorkerRegistration*, |
| const NotificationDatabaseData&, |
| NotificationDispatchCompleteCallback)>; |
| |
| // Derives a PersistentNotificationStatus from the ServiceWorkerStatusCode. |
| PersistentNotificationStatus ConvertServiceWorkerStatus( |
| blink::ServiceWorkerStatusCode service_worker_status) { |
| #if BUILDFLAG(IS_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: " |
| << blink::ServiceWorkerStatusToString(service_worker_status); |
| #endif |
| switch (service_worker_status) { |
| case blink::ServiceWorkerStatusCode::kOk: |
| return PersistentNotificationStatus::kSuccess; |
| case blink::ServiceWorkerStatusCode::kErrorEventWaitUntilRejected: |
| return PersistentNotificationStatus::kWaitUntilRejected; |
| case blink::ServiceWorkerStatusCode::kErrorFailed: |
| case blink::ServiceWorkerStatusCode::kErrorAbort: |
| case blink::ServiceWorkerStatusCode::kErrorStartWorkerFailed: |
| case blink::ServiceWorkerStatusCode::kErrorProcessNotFound: |
| case blink::ServiceWorkerStatusCode::kErrorNotFound: |
| 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::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: |
| return PersistentNotificationStatus::kServiceWorkerError; |
| } |
| NOTREACHED(); |
| } |
| |
| // To be called when a notification event has finished with a |
| // blink::ServiceWorkerStatusCode result. Will run or post a task to call |
| // |dispatch_complete_callback| on the UI thread with a |
| // PersistentNotificationStatus derived from the service worker status. |
| void ServiceWorkerNotificationEventFinished( |
| NotificationDispatchCompleteCallback dispatch_complete_callback, |
| blink::ServiceWorkerStatusCode service_worker_status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| std::move(dispatch_complete_callback) |
| .Run(ConvertServiceWorkerStatus(service_worker_status), |
| service_worker_status); |
| } |
| |
| // Dispatches the given notification action event on |
| // |service_worker_registration| if the registration was available. |
| void DispatchNotificationEventOnRegistration( |
| const NotificationDatabaseData& notification_database_data, |
| NotificationOperationCallback dispatch_event_action, |
| NotificationDispatchCompleteCallback dispatch_complete_callback, |
| blink::ServiceWorkerStatusCode service_worker_status, |
| scoped_refptr<ServiceWorkerRegistration> service_worker_registration) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| #if BUILDFLAG(IS_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: " |
| << blink::ServiceWorkerStatusToString(service_worker_status); |
| #endif |
| if (service_worker_status == blink::ServiceWorkerStatusCode::kOk) { |
| DCHECK(service_worker_registration->active_version()); |
| |
| std::move(dispatch_event_action) |
| .Run(service_worker_registration.get(), notification_database_data, |
| std::move(dispatch_complete_callback)); |
| return; |
| } |
| |
| PersistentNotificationStatus status = PersistentNotificationStatus::kSuccess; |
| switch (service_worker_status) { |
| case blink::ServiceWorkerStatusCode::kErrorNotFound: |
| status = PersistentNotificationStatus::kServiceWorkerMissing; |
| break; |
| case blink::ServiceWorkerStatusCode::kErrorFailed: |
| 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: |
| status = PersistentNotificationStatus::kServiceWorkerError; |
| break; |
| case blink::ServiceWorkerStatusCode::kOk: |
| NOTREACHED(); |
| } |
| |
| GetUIThreadTaskRunner({})->PostTask( |
| FROM_HERE, base::BindOnce(std::move(dispatch_complete_callback), status, |
| service_worker_status)); |
| } |
| |
| // Finds the ServiceWorkerRegistration associated with the |origin| and |
| // |service_worker_registration_id|. Must be called on the UI thread. |
| void FindServiceWorkerRegistration( |
| const url::Origin& origin, |
| const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context, |
| NotificationOperationCallback notification_action_callback, |
| NotificationDispatchCompleteCallback dispatch_complete_callback, |
| bool success, |
| const NotificationDatabaseData& notification_database_data) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| #if BUILDFLAG(IS_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) { |
| std::move(dispatch_complete_callback) |
| .Run(PersistentNotificationStatus::kDatabaseError, |
| blink::ServiceWorkerStatusCode::kOk); |
| return; |
| } |
| |
| // If Push Notification becomes usable from a 3p context then |
| // NotificationDatabaseData should be changed to use StorageKey. |
| service_worker_context->FindReadyRegistrationForId( |
| notification_database_data.service_worker_registration_id, |
| blink::StorageKey::CreateFirstParty(origin), |
| base::BindOnce(&DispatchNotificationEventOnRegistration, |
| notification_database_data, |
| std::move(notification_action_callback), |
| std::move(dispatch_complete_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, |
| PlatformNotificationContext::Interaction interaction, |
| const scoped_refptr<ServiceWorkerContextWrapper>& service_worker_context, |
| const scoped_refptr<PlatformNotificationContext>& notification_context, |
| NotificationOperationCallback notification_read_callback, |
| NotificationDispatchCompleteCallback dispatch_complete_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| notification_context->ReadNotificationDataAndRecordInteraction( |
| notification_id, origin, interaction, |
| base::BindOnce(&FindServiceWorkerRegistration, |
| url::Origin::Create(origin), service_worker_context, |
| std::move(notification_read_callback), |
| std::move(dispatch_complete_callback))); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| // Dispatches the notificationclick event on |service_worker|. |
| void DispatchNotificationClickEventOnWorker( |
| const scoped_refptr<ServiceWorkerVersion>& service_worker, |
| const NotificationDatabaseData& notification_database_data, |
| const std::optional<int>& action_index, |
| const std::optional<std::u16string>& reply, |
| ServiceWorkerVersion::StatusCallback callback, |
| blink::ServiceWorkerStatusCode start_worker_status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (start_worker_status != blink::ServiceWorkerStatusCode::kOk) { |
| std::move(callback).Run(start_worker_status); |
| return; |
| } |
| |
| int request_id = service_worker->StartRequest( |
| ServiceWorkerMetrics::EventType::NOTIFICATION_CLICK, std::move(callback)); |
| |
| int action_index_int = -1 /* no value */; |
| if (action_index.has_value()) |
| action_index_int = action_index.value(); |
| |
| service_worker->endpoint()->DispatchNotificationClickEvent( |
| notification_database_data.notification_id, |
| notification_database_data.notification_data, action_index_int, reply, |
| service_worker->CreateSimpleEventCallback(request_id)); |
| } |
| |
| // Dispatches the notification click event on the |service_worker_registration|. |
| void DoDispatchNotificationClickEvent( |
| const std::optional<int>& action_index, |
| const std::optional<std::u16string>& reply, |
| const scoped_refptr<PlatformNotificationContext>& notification_context, |
| BrowserContext* browser_context, |
| const ServiceWorkerRegistration* service_worker_registration, |
| const NotificationDatabaseData& notification_database_data, |
| NotificationDispatchCompleteCallback dispatch_complete_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| notifications::LogNotificationClickedEventToDevTools( |
| browser_context, notification_database_data, action_index, reply); |
| |
| service_worker_registration->active_version()->RunAfterStartWorker( |
| ServiceWorkerMetrics::EventType::NOTIFICATION_CLICK, |
| base::BindOnce( |
| &DispatchNotificationClickEventOnWorker, |
| base::WrapRefCounted(service_worker_registration->active_version()), |
| notification_database_data, action_index, reply, |
| base::BindOnce(&ServiceWorkerNotificationEventFinished, |
| std::move(dispatch_complete_callback)))); |
| } |
| |
| // ----------------------------------------------------------------------------- |
| |
| // Called when the notification data has been deleted to finish the notification |
| // close event. |
| void OnPersistentNotificationDataDeleted( |
| blink::ServiceWorkerStatusCode service_worker_status, |
| NotificationDispatchCompleteCallback dispatch_complete_callback, |
| bool success) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| PersistentNotificationStatus status = |
| success ? PersistentNotificationStatus::kSuccess |
| : PersistentNotificationStatus::kDatabaseError; |
| if (service_worker_status != blink::ServiceWorkerStatusCode::kOk) |
| status = ConvertServiceWorkerStatus(service_worker_status); |
| std::move(dispatch_complete_callback).Run(status, service_worker_status); |
| } |
| |
| // 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, |
| NotificationDispatchCompleteCallback dispatch_complete_callback, |
| blink::ServiceWorkerStatusCode status_code) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| notification_context->DeleteNotificationData( |
| notification_id, origin, |
| /* close_notification= */ false, |
| base::BindOnce(&OnPersistentNotificationDataDeleted, status_code, |
| std::move(dispatch_complete_callback))); |
| } |
| |
| // Dispatches the notificationclose event on |service_worker|. |
| void DispatchNotificationCloseEventOnWorker( |
| const scoped_refptr<ServiceWorkerVersion>& service_worker, |
| const NotificationDatabaseData& notification_database_data, |
| ServiceWorkerVersion::StatusCallback callback, |
| blink::ServiceWorkerStatusCode start_worker_status) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (start_worker_status != blink::ServiceWorkerStatusCode::kOk) { |
| std::move(callback).Run(start_worker_status); |
| return; |
| } |
| |
| int request_id = service_worker->StartRequest( |
| ServiceWorkerMetrics::EventType::NOTIFICATION_CLOSE, std::move(callback)); |
| |
| service_worker->endpoint()->DispatchNotificationCloseEvent( |
| notification_database_data.notification_id, |
| notification_database_data.notification_data, |
| service_worker->CreateSimpleEventCallback(request_id)); |
| } |
| |
| // Dispatches the notification close event on the service worker registration. |
| void DoDispatchNotificationCloseEvent( |
| const std::string& notification_id, |
| bool by_user, |
| const scoped_refptr<PlatformNotificationContext>& notification_context, |
| BrowserContext* browser_context, |
| const ServiceWorkerRegistration* service_worker_registration, |
| const NotificationDatabaseData& notification_database_data, |
| NotificationDispatchCompleteCallback dispatch_complete_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (by_user) { |
| service_worker_registration->active_version()->RunAfterStartWorker( |
| ServiceWorkerMetrics::EventType::NOTIFICATION_CLOSE, |
| base::BindOnce( |
| &DispatchNotificationCloseEventOnWorker, |
| base::WrapRefCounted(service_worker_registration->active_version()), |
| notification_database_data, |
| base::BindOnce(&DeleteNotificationDataFromDatabase, notification_id, |
| notification_database_data.origin, |
| notification_context, |
| std::move(dispatch_complete_callback)))); |
| } else { |
| DeleteNotificationDataFromDatabase( |
| notification_id, notification_database_data.origin, |
| notification_context, std::move(dispatch_complete_callback), |
| blink::ServiceWorkerStatusCode::kOk); |
| } |
| } |
| |
| // 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 PlatformNotificationContext::Interaction interaction, |
| NotificationOperationCallbackWithContext notification_action_callback, |
| NotificationDispatchCompleteCallback dispatch_complete_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(!notification_id.empty()); |
| DCHECK(origin.is_valid()); |
| |
| StoragePartition* partition = |
| browser_context->GetStoragePartitionForUrl(origin); |
| |
| scoped_refptr<ServiceWorkerContextWrapper> service_worker_context = |
| static_cast<ServiceWorkerContextWrapper*>( |
| partition->GetServiceWorkerContext()); |
| scoped_refptr<PlatformNotificationContext> notification_context = |
| partition->GetPlatformNotificationContext(); |
| |
| ReadNotificationDatabaseData( |
| notification_id, origin, interaction, service_worker_context, |
| notification_context, |
| base::BindOnce(std::move(notification_action_callback), |
| notification_context, browser_context), |
| std::move(dispatch_complete_callback)); |
| } |
| |
| void OnDispatchNotificationClickEventComplete( |
| PersistentNotificationDispatchCompleteCallback dispatch_complete_callback, |
| PersistentNotificationStatus status, |
| blink::ServiceWorkerStatusCode service_worker_status) { |
| std::move(dispatch_complete_callback).Run(status); |
| } |
| |
| void OnDispatchNotificationCloseEventComplete( |
| PersistentNotificationDispatchCompleteCallback dispatch_complete_callback, |
| PersistentNotificationStatus status, |
| blink::ServiceWorkerStatusCode service_worker_status) { |
| std::move(dispatch_complete_callback).Run(status); |
| } |
| |
| } // namespace |
| |
| // static |
| NotificationEventDispatcher* NotificationEventDispatcher::GetInstance() { |
| return NotificationEventDispatcherImpl::GetInstance(); |
| } |
| |
| NotificationEventDispatcherImpl* |
| NotificationEventDispatcherImpl::GetInstance() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return base::Singleton<NotificationEventDispatcherImpl>::get(); |
| } |
| |
| NotificationEventDispatcherImpl::NotificationEventDispatcherImpl() = default; |
| NotificationEventDispatcherImpl::~NotificationEventDispatcherImpl() = default; |
| |
| NotificationEventDispatcherImpl::NonPersistentNotificationListenerInfo:: |
| NonPersistentNotificationListenerInfo( |
| mojo::Remote<blink::mojom::NonPersistentNotificationListener> remote, |
| WeakDocumentPtr document, |
| RenderProcessHost::NotificationServiceCreatorType creator_type) |
| : remote(std::move(remote)), |
| document(document), |
| creator_type(creator_type) {} |
| |
| NotificationEventDispatcherImpl::NonPersistentNotificationListenerInfo:: |
| NonPersistentNotificationListenerInfo( |
| NotificationEventDispatcherImpl::NonPersistentNotificationListenerInfo&& |
| info) = default; |
| |
| NotificationEventDispatcherImpl::NonPersistentNotificationListenerInfo:: |
| ~NonPersistentNotificationListenerInfo() = default; |
| |
| base::optional_ref<content::NotificationEventDispatcherImpl:: |
| NonPersistentNotificationListenerInfo> |
| NotificationEventDispatcherImpl::GetListenerIfNotifiable( |
| const std::string& notification_id) { |
| auto listener = non_persistent_notification_listeners_.find(notification_id); |
| |
| // If there is no listener registered for this notification id, no event |
| // should be dispatched. |
| if (listener == non_persistent_notification_listeners_.end()) { |
| return std::nullopt; |
| } |
| |
| // The non-persistent notification should not be created by service workers. |
| DCHECK(listener->second.creator_type != |
| RenderProcessHost::NotificationServiceCreatorType::kServiceWorker); |
| |
| RenderFrameHost* rfh = listener->second.document.AsRenderFrameHostIfValid(); |
| if (!rfh) { |
| switch (listener->second.creator_type) { |
| case RenderProcessHost::NotificationServiceCreatorType::kDedicatedWorker: |
| case RenderProcessHost::NotificationServiceCreatorType::kDocument: { |
| // The weak document pointer should be pointing to the document that the |
| // notification service is communicating with, if it's empty, it's |
| // possible that the document is already destroyed. In this case, the |
| // notification event shouldn't be dispatched. |
| return std::nullopt; |
| } |
| case RenderProcessHost::NotificationServiceCreatorType::kSharedWorker: { |
| // In this case, the weak document pointer is always null and we |
| // shouldn't block the notification. |
| return listener->second; |
| } |
| case RenderProcessHost::NotificationServiceCreatorType::kServiceWorker: { |
| NOTREACHED(); |
| } |
| } |
| } |
| |
| // If the associated document is currently in back/forward cache, the |
| // function returns nullopt to prevent the listener from being triggered. |
| // TODO: in the future, this could be improved to cover more lifecycle |
| // state. see: https://crrev.com/c/3861889/comment/e1759c1e_4dd15e4e/ |
| if (rfh->IsInLifecycleState( |
| RenderFrameHost::LifecycleState::kInBackForwardCache)) { |
| return std::nullopt; |
| } |
| return listener->second; |
| } |
| |
| void NotificationEventDispatcherImpl::DispatchNotificationClickEvent( |
| BrowserContext* browser_context, |
| const std::string& notification_id, |
| const GURL& origin, |
| const std::optional<int>& action_index, |
| const std::optional<std::u16string>& reply, |
| NotificationDispatchCompleteCallback dispatch_complete_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| PlatformNotificationContext::Interaction interaction = |
| action_index.has_value() |
| ? PlatformNotificationContext::Interaction::ACTION_BUTTON_CLICKED |
| : PlatformNotificationContext::Interaction::CLICKED; |
| |
| DispatchNotificationEvent( |
| browser_context, notification_id, origin, interaction, |
| base::BindOnce(&DoDispatchNotificationClickEvent, action_index, reply), |
| base::BindOnce(&OnDispatchNotificationClickEventComplete, |
| std::move(dispatch_complete_callback))); |
| } |
| |
| void NotificationEventDispatcherImpl::DispatchNotificationCloseEvent( |
| BrowserContext* browser_context, |
| const std::string& notification_id, |
| const GURL& origin, |
| bool by_user, |
| NotificationDispatchCompleteCallback dispatch_complete_callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| DispatchNotificationEvent( |
| browser_context, notification_id, origin, |
| PlatformNotificationContext::Interaction::CLOSED, |
| base::BindOnce(&DoDispatchNotificationCloseEvent, notification_id, |
| by_user), |
| base::BindOnce(&OnDispatchNotificationCloseEventComplete, |
| std::move(dispatch_complete_callback))); |
| } |
| |
| void NotificationEventDispatcherImpl::RegisterNonPersistentNotificationListener( |
| const std::string& notification_id, |
| mojo::PendingRemote<blink::mojom::NonPersistentNotificationListener> |
| event_listener_remote, |
| const WeakDocumentPtr& event_document_ptr, |
| const RenderProcessHost::NotificationServiceCreatorType creator_type) { |
| mojo::Remote<blink::mojom::NonPersistentNotificationListener> bound_remote( |
| std::move(event_listener_remote)); |
| |
| // Observe connection errors, which occur when the JavaScript object or the |
| // renderer hosting them goes away. (For example through navigation.) The |
| // listener gets freed together with |this|, thus the Unretained is safe. |
| bound_remote.set_disconnect_handler(base::BindOnce( |
| &NotificationEventDispatcherImpl:: |
| HandleConnectionErrorForNonPersistentNotificationListener, |
| base::Unretained(this), notification_id)); |
| |
| // Dispatch the close event for any previously displayed notification with |
| // the same notification id. This happens whenever a non-persistent |
| // notification is replaced (by creating another with the same tag), since |
| // from the JavaScript point of view there will be two notification objects, |
| // and the old one needs to receive a close event before the new one |
| // receives a show event. |
| DispatchNonPersistentCloseEvent(notification_id, base::DoNothing()); |
| |
| if (non_persistent_notification_listeners_.count(notification_id)) { |
| non_persistent_notification_listeners_.erase(notification_id); |
| } |
| non_persistent_notification_listeners_.emplace( |
| std::piecewise_construct, std::forward_as_tuple(notification_id), |
| std::forward_as_tuple(std::move(bound_remote), event_document_ptr, |
| creator_type)); |
| } |
| |
| // Only fire the notification listeners (including show, click and |
| // close) when it exists in the map and the document is currently |
| // not in back/forward cache. |
| // See https://crbug.com/1350944 |
| void NotificationEventDispatcherImpl::DispatchNonPersistentShowEvent( |
| const std::string& notification_id) { |
| auto listener = GetListenerIfNotifiable(notification_id); |
| if (listener.has_value()) { |
| listener->remote->OnShow(); |
| } |
| } |
| |
| void NotificationEventDispatcherImpl::DispatchNonPersistentClickEvent( |
| const std::string& notification_id, |
| NotificationClickEventCallback callback) { |
| auto listener = GetListenerIfNotifiable(notification_id); |
| if (listener.has_value()) { |
| listener->remote->OnClick( |
| base::BindOnce(std::move(callback), true /* success */)); |
| } else { |
| std::move(callback).Run(false /* success */); |
| } |
| } |
| |
| void NotificationEventDispatcherImpl::DispatchNonPersistentCloseEvent( |
| const std::string& notification_id, |
| base::OnceClosure completed_closure) { |
| auto listener = GetListenerIfNotifiable(notification_id); |
| if (listener.has_value()) { |
| // Listeners get freed together with `this`, thus the Unretained is safe. |
| listener->remote->OnClose(base::BindOnce( |
| &NotificationEventDispatcherImpl::OnNonPersistentCloseComplete, |
| base::Unretained(this), notification_id, std::move(completed_closure))); |
| } else { |
| std::move(completed_closure).Run(); |
| } |
| } |
| |
| void NotificationEventDispatcherImpl::OnNonPersistentCloseComplete( |
| const std::string& notification_id, |
| base::OnceClosure completed_closure) { |
| non_persistent_notification_listeners_.erase(notification_id); |
| std::move(completed_closure).Run(); |
| } |
| |
| void NotificationEventDispatcherImpl:: |
| HandleConnectionErrorForNonPersistentNotificationListener( |
| const std::string& notification_id) { |
| DCHECK(non_persistent_notification_listeners_.count(notification_id)); |
| non_persistent_notification_listeners_.erase(notification_id); |
| } |
| |
| } // namespace content |