| // Copyright 2016 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/blink_notification_service_impl.h" |
| |
| #include "base/bind.h" |
| #include "base/bind_helpers.h" |
| #include "base/callback_helpers.h" |
| #include "base/logging.h" |
| #include "base/strings/string16.h" |
| #include "content/browser/notifications/notification_event_dispatcher_impl.h" |
| #include "content/browser/notifications/platform_notification_context_impl.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/browser/notification_database_data.h" |
| #include "content/public/browser/permission_manager.h" |
| #include "content/public/browser/permission_type.h" |
| #include "content/public/browser/platform_notification_service.h" |
| #include "content/public/common/content_client.h" |
| #include "content/public/common/notification_resources.h" |
| #include "content/public/common/platform_notification_data.h" |
| #include "third_party/blink/public/common/service_worker/service_worker_status_code.h" |
| #include "url/gurl.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| // Returns the implementation of the PlatformNotificationService. May be NULL. |
| PlatformNotificationService* Service() { |
| return GetContentClient()->browser()->GetPlatformNotificationService(); |
| } |
| |
| } // namespace |
| |
| using blink::mojom::PersistentNotificationError; |
| |
| BlinkNotificationServiceImpl::BlinkNotificationServiceImpl( |
| PlatformNotificationContextImpl* notification_context, |
| BrowserContext* browser_context, |
| scoped_refptr<ServiceWorkerContextWrapper> service_worker_context, |
| const url::Origin& origin, |
| mojo::InterfaceRequest<blink::mojom::NotificationService> request) |
| : notification_context_(notification_context), |
| browser_context_(browser_context), |
| service_worker_context_(std::move(service_worker_context)), |
| origin_(origin), |
| binding_(this, std::move(request)) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(notification_context_); |
| DCHECK(browser_context_); |
| |
| binding_.set_connection_error_handler(base::BindOnce( |
| &BlinkNotificationServiceImpl::OnConnectionError, |
| base::Unretained(this) /* the channel is owned by |this| */)); |
| } |
| |
| BlinkNotificationServiceImpl::~BlinkNotificationServiceImpl() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| } |
| |
| void BlinkNotificationServiceImpl::GetPermissionStatus( |
| GetPermissionStatusCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!Service()) { |
| std::move(callback).Run(blink::mojom::PermissionStatus::DENIED); |
| return; |
| } |
| |
| std::move(callback).Run(CheckPermissionStatus()); |
| } |
| |
| void BlinkNotificationServiceImpl::OnConnectionError() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| notification_context_->RemoveService(this); |
| // |this| has now been deleted. |
| } |
| |
| void BlinkNotificationServiceImpl::DisplayNonPersistentNotification( |
| const std::string& token, |
| const PlatformNotificationData& platform_notification_data, |
| const NotificationResources& notification_resources, |
| blink::mojom::NonPersistentNotificationListenerPtr event_listener_ptr) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!Service()) |
| return; |
| |
| if (CheckPermissionStatus() != blink::mojom::PermissionStatus::GRANTED) |
| return; |
| |
| std::string notification_id = |
| notification_context_->notification_id_generator() |
| ->GenerateForNonPersistentNotification(origin_, token); |
| |
| NotificationEventDispatcherImpl* event_dispatcher = |
| NotificationEventDispatcherImpl::GetInstance(); |
| event_dispatcher->RegisterNonPersistentNotificationListener( |
| notification_id, std::move(event_listener_ptr)); |
| |
| Service()->DisplayNotification(browser_context_, notification_id, |
| origin_.GetURL(), platform_notification_data, |
| notification_resources); |
| } |
| |
| void BlinkNotificationServiceImpl::CloseNonPersistentNotification( |
| const std::string& token) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!Service()) |
| return; |
| |
| if (CheckPermissionStatus() != blink::mojom::PermissionStatus::GRANTED) |
| return; |
| |
| std::string notification_id = |
| notification_context_->notification_id_generator() |
| ->GenerateForNonPersistentNotification(origin_, token); |
| |
| Service()->CloseNotification(browser_context_, notification_id); |
| |
| // TODO(https://crbug.com/442141): Pass a callback here to focus the tab |
| // which created the notification, unless the event is canceled. |
| NotificationEventDispatcherImpl::GetInstance() |
| ->DispatchNonPersistentCloseEvent(notification_id, base::DoNothing()); |
| } |
| |
| blink::mojom::PermissionStatus |
| BlinkNotificationServiceImpl::CheckPermissionStatus() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!browser_context_->GetPermissionManager()) |
| return blink::mojom::PermissionStatus::DENIED; |
| |
| return browser_context_->GetPermissionManager()->GetPermissionStatus( |
| PermissionType::NOTIFICATIONS, origin_.GetURL(), origin_.GetURL()); |
| } |
| |
| void BlinkNotificationServiceImpl::DisplayPersistentNotification( |
| int64_t service_worker_registration_id, |
| const PlatformNotificationData& platform_notification_data, |
| const NotificationResources& notification_resources, |
| DisplayPersistentNotificationCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!Service()) { |
| std::move(callback).Run(PersistentNotificationError::INTERNAL_ERROR); |
| return; |
| } |
| |
| if (CheckPermissionStatus() != blink::mojom::PermissionStatus::GRANTED) { |
| std::move(callback).Run(PersistentNotificationError::PERMISSION_DENIED); |
| return; |
| } |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&BlinkNotificationServiceImpl:: |
| DisplayPersistentNotificationOnIOThread, |
| weak_factory_for_io_.GetWeakPtr(), |
| service_worker_registration_id, platform_notification_data, |
| notification_resources, std::move(callback))); |
| } |
| |
| void BlinkNotificationServiceImpl::DisplayPersistentNotificationOnIOThread( |
| int64_t service_worker_registration_id, |
| const PlatformNotificationData& platform_notification_data, |
| const NotificationResources& notification_resources, |
| DisplayPersistentNotificationCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| // TODO(awdf): Necessary to validate resources here? |
| |
| NotificationDatabaseData database_data; |
| database_data.origin = origin_.GetURL(); |
| database_data.service_worker_registration_id = service_worker_registration_id; |
| database_data.notification_data = platform_notification_data; |
| |
| notification_context_->WriteNotificationData( |
| origin_.GetURL(), database_data, |
| base::AdaptCallbackForRepeating(base::BindOnce( |
| &BlinkNotificationServiceImpl:: |
| DisplayPersistentNotificationWithIdOnIOThread, |
| weak_factory_for_io_.GetWeakPtr(), service_worker_registration_id, |
| platform_notification_data, notification_resources, |
| std::move(callback)))); |
| } |
| |
| void BlinkNotificationServiceImpl:: |
| DisplayPersistentNotificationWithIdOnIOThread( |
| int64_t service_worker_registration_id, |
| const PlatformNotificationData& platform_notification_data, |
| const NotificationResources& notification_resources, |
| DisplayPersistentNotificationCallback callback, |
| bool success, |
| const std::string& notification_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| if (!success) { |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce(std::move(callback), |
| PersistentNotificationError::INTERNAL_ERROR)); |
| return; |
| } |
| |
| service_worker_context_->FindReadyRegistrationForId( |
| service_worker_registration_id, origin_.GetURL(), |
| base::BindOnce( |
| &BlinkNotificationServiceImpl:: |
| DisplayPersistentNotificationWithServiceWorkerOnIOThread, |
| weak_factory_for_io_.GetWeakPtr(), notification_id, |
| platform_notification_data, notification_resources, |
| std::move(callback))); |
| } |
| |
| void BlinkNotificationServiceImpl:: |
| DisplayPersistentNotificationWithServiceWorkerOnIOThread( |
| const std::string& notification_id, |
| const PlatformNotificationData& platform_notification_data, |
| const NotificationResources& notification_resources, |
| DisplayPersistentNotificationCallback callback, |
| blink::ServiceWorkerStatusCode service_worker_status, |
| scoped_refptr<ServiceWorkerRegistration> registration) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| PersistentNotificationError error = |
| PersistentNotificationError::INTERNAL_ERROR; |
| |
| // Display the notification if the Service Worker's origin matches the origin |
| // of the notification's sender. |
| if (service_worker_status == blink::ServiceWorkerStatusCode::kOk && |
| registration->pattern().GetOrigin() == origin_.GetURL()) { |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce( |
| &PlatformNotificationService::DisplayPersistentNotification, |
| base::Unretained(Service()), browser_context_, notification_id, |
| registration->pattern(), origin_.GetURL(), |
| platform_notification_data, notification_resources)); |
| |
| error = PersistentNotificationError::NONE; |
| } |
| |
| BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, |
| base::BindOnce(std::move(callback), error)); |
| } |
| |
| void BlinkNotificationServiceImpl::ClosePersistentNotification( |
| const std::string& notification_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!Service()) |
| return; |
| |
| if (CheckPermissionStatus() != blink::mojom::PermissionStatus::GRANTED) |
| return; |
| |
| Service()->ClosePersistentNotification(browser_context_, notification_id); |
| |
| // Deleting the data associated with |notification_id| from the notification |
| // database has to be done on the IO thread, but there's no reason to postpone |
| // removing the notification from the user's display until that's done. |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&PlatformNotificationContextImpl::DeleteNotificationData, |
| notification_context_, notification_id, origin_.GetURL(), |
| base::DoNothing())); |
| } |
| |
| void BlinkNotificationServiceImpl::GetNotifications( |
| int64_t service_worker_registration_id, |
| const std::string& filter_tag, |
| GetNotificationsCallback callback) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!Service() || |
| CheckPermissionStatus() != blink::mojom::PermissionStatus::GRANTED) { |
| // No permission has been granted for the given origin. It is harmless to |
| // try to get notifications without permission, so return empty vectors |
| // indicating that no (accessible) notifications exist at this time. |
| std::move(callback).Run(std::vector<std::string>(), |
| std::vector<PlatformNotificationData>()); |
| return; |
| } |
| |
| auto read_notification_data_callback = base::BindOnce( |
| &BlinkNotificationServiceImpl::DidGetNotificationsOnIOThread, |
| weak_factory_for_io_.GetWeakPtr(), filter_tag, std::move(callback)); |
| |
| BrowserThread::PostTask( |
| BrowserThread::IO, FROM_HERE, |
| base::BindOnce(&PlatformNotificationContextImpl:: |
| ReadAllNotificationDataForServiceWorkerRegistration, |
| notification_context_, origin_.GetURL(), |
| service_worker_registration_id, |
| base::AdaptCallbackForRepeating( |
| std::move(read_notification_data_callback)))); |
| } |
| |
| void BlinkNotificationServiceImpl::DidGetNotificationsOnIOThread( |
| const std::string& filter_tag, |
| GetNotificationsCallback callback, |
| bool success, |
| const std::vector<NotificationDatabaseData>& notifications) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| |
| std::vector<std::string> ids; |
| std::vector<PlatformNotificationData> datas; |
| |
| for (const NotificationDatabaseData& database_data : notifications) { |
| // An empty filter tag matches all, else we need an exact match. |
| if (filter_tag.empty() || |
| filter_tag == database_data.notification_data.tag) { |
| ids.push_back(database_data.notification_id); |
| datas.push_back(database_data.notification_data); |
| } |
| } |
| |
| // Make sure to invoke the |callback| on the UI thread again. |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::BindOnce(std::move(callback), std::move(ids), std::move(datas))); |
| } |
| |
| } // namespace content |