1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/notifications/platform_notification_service_impl.h"
7 #include "base/metrics/histogram_macros.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "chrome/browser/browser_process.h"
11 #include "chrome/browser/notifications/desktop_notification_profile_util.h"
12 #include "chrome/browser/notifications/notification_object_proxy.h"
13 #include "chrome/browser/notifications/notification_ui_manager.h"
14 #include "chrome/browser/notifications/persistent_notification_delegate.h"
15 #include "chrome/browser/profiles/profile.h"
16 #include "chrome/browser/profiles/profile_io_data.h"
17 #include "chrome/common/pref_names.h"
18 #include "components/content_settings/core/browser/host_content_settings_map.h"
19 #include "components/content_settings/core/common/content_settings.h"
20 #include "content/public/browser/browser_thread.h"
21 #include "content/public/browser/desktop_notification_delegate.h"
22 #include "content/public/browser/notification_event_dispatcher.h"
23 #include "content/public/browser/platform_notification_context.h"
24 #include "content/public/browser/storage_partition.h"
25 #include "content/public/common/platform_notification_data.h"
26 #include "net/base/net_util.h"
27 #include "ui/message_center/notifier_settings.h"
28 #include "url/url_constants.h"
30 #if defined(ENABLE_EXTENSIONS)
31 #include "chrome/browser/notifications/desktop_notification_service.h"
32 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
33 #include "extensions/browser/extension_registry.h"
34 #include "extensions/browser/extension_system.h"
35 #include "extensions/browser/info_map.h"
36 #include "extensions/common/constants.h"
37 #include "extensions/common/extension_set.h"
38 #include "extensions/common/permissions/api_permission.h"
39 #include "extensions/common/permissions/permissions_data.h"
42 #if defined(OS_ANDROID)
43 #include "base/strings/string_number_conversions.h"
46 using content::BrowserContext
;
47 using content::BrowserThread
;
48 using content::PlatformNotificationContext
;
49 using message_center::NotifierId
;
53 // Callback to provide when deleting the data associated with persistent Web
54 // Notifications from the notification database.
55 void OnPersistentNotificationDataDeleted(bool success
) {
56 UMA_HISTOGRAM_BOOLEAN("Notifications.PersistentNotificationDataDeleted",
60 // Persistent notifications fired through the delegate do not care about the
61 // lifetime of the Service Worker responsible for executing the event.
62 void OnEventDispatchComplete(content::PersistentNotificationStatus status
) {
63 UMA_HISTOGRAM_ENUMERATION(
64 "Notifications.PersistentWebNotificationClickResult", status
,
65 content::PersistentNotificationStatus::
66 PERSISTENT_NOTIFICATION_STATUS_MAX
);
69 void CancelNotification(const std::string
& id
, ProfileID profile_id
) {
70 PlatformNotificationServiceImpl::GetInstance()
71 ->GetNotificationUIManager()->CancelById(id
, profile_id
);
77 PlatformNotificationServiceImpl
*
78 PlatformNotificationServiceImpl::GetInstance() {
79 return Singleton
<PlatformNotificationServiceImpl
>::get();
82 PlatformNotificationServiceImpl::PlatformNotificationServiceImpl()
83 : notification_ui_manager_for_tests_(nullptr) {}
85 PlatformNotificationServiceImpl::~PlatformNotificationServiceImpl() {}
87 void PlatformNotificationServiceImpl::OnPersistentNotificationClick(
88 BrowserContext
* browser_context
,
89 int64_t persistent_notification_id
,
90 const GURL
& origin
) const {
91 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
92 content::NotificationEventDispatcher::GetInstance()
93 ->DispatchNotificationClickEvent(
95 persistent_notification_id
,
97 base::Bind(&OnEventDispatchComplete
));
100 void PlatformNotificationServiceImpl::OnPersistentNotificationClose(
101 BrowserContext
* browser_context
,
102 int64_t persistent_notification_id
,
103 const GURL
& origin
) const {
104 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
105 PlatformNotificationContext
* context
=
106 BrowserContext::GetStoragePartitionForSite(browser_context
, origin
)
107 ->GetPlatformNotificationContext();
109 BrowserThread::PostTask(
112 base::Bind(&PlatformNotificationContext::DeleteNotificationData
,
114 persistent_notification_id
,
116 base::Bind(&OnPersistentNotificationDataDeleted
)));
119 blink::WebNotificationPermission
120 PlatformNotificationServiceImpl::CheckPermissionOnUIThread(
121 BrowserContext
* browser_context
,
123 int render_process_id
) {
124 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
126 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
129 #if defined(ENABLE_EXTENSIONS)
130 // Extensions support an API permission named "notification". This will grant
131 // not only grant permission for using the Chrome App extension API, but also
132 // for the Web Notification API.
133 extensions::ExtensionRegistry
* registry
=
134 extensions::ExtensionRegistry::Get(browser_context
);
135 extensions::ProcessMap
* process_map
=
136 extensions::ProcessMap::Get(browser_context
);
137 extensions::ExtensionSet extensions
;
139 DesktopNotificationService
* desktop_notification_service
=
140 DesktopNotificationServiceFactory::GetForProfile(profile
);
141 DCHECK(desktop_notification_service
);
143 // If |origin| is an enabled extension, only select that one. Otherwise select
144 // all extensions whose web content matches |origin|.
145 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
146 const extensions::Extension
* extension
= registry
->GetExtensionById(
147 origin
.host(), extensions::ExtensionRegistry::ENABLED
);
149 extensions
.Insert(extension
);
151 for (const auto& extension
: registry
->enabled_extensions()) {
152 if (extension
->web_extent().MatchesSecurityOrigin(origin
))
153 extensions
.Insert(extension
);
157 // Check if any of the selected extensions have the "notification" API
158 // permission, are active in |render_process_id| and has not been manually
159 // disabled by the user. If all of that is true, grant permission.
160 for (const auto& extension
: extensions
) {
161 if (!extension
->permissions_data()->HasAPIPermission(
162 extensions::APIPermission::kNotifications
))
165 if (!process_map
->Contains(extension
->id(), render_process_id
))
168 NotifierId
notifier_id(NotifierId::APPLICATION
, extension
->id());
169 if (!desktop_notification_service
->IsNotifierEnabled(notifier_id
))
172 return blink::WebNotificationPermissionAllowed
;
176 ContentSetting setting
=
177 DesktopNotificationProfileUtil::GetContentSetting(profile
, origin
);
179 if (setting
== CONTENT_SETTING_ALLOW
)
180 return blink::WebNotificationPermissionAllowed
;
181 if (setting
== CONTENT_SETTING_BLOCK
)
182 return blink::WebNotificationPermissionDenied
;
184 return blink::WebNotificationPermissionDefault
;
187 blink::WebNotificationPermission
188 PlatformNotificationServiceImpl::CheckPermissionOnIOThread(
189 content::ResourceContext
* resource_context
,
191 int render_process_id
) {
192 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
194 ProfileIOData
* io_data
= ProfileIOData::FromResourceContext(resource_context
);
195 #if defined(ENABLE_EXTENSIONS)
196 extensions::InfoMap
* extension_info_map
= io_data
->GetExtensionInfoMap();
198 // We want to see if there is an extension that hasn't been manually disabled
199 // that has the notifications permission and applies to this security origin.
200 // First, get the list of extensions with permission for the origin.
201 extensions::ExtensionSet extensions
;
202 extension_info_map
->GetExtensionsWithAPIPermissionForSecurityOrigin(
205 extensions::APIPermission::kNotifications
,
207 for (const auto& extension
: extensions
) {
208 if (!extension_info_map
->AreNotificationsDisabled(extension
->id()))
209 return blink::WebNotificationPermissionAllowed
;
213 // No enabled extensions exist, so check the normal host content settings.
214 HostContentSettingsMap
* host_content_settings_map
=
215 io_data
->GetHostContentSettingsMap();
216 ContentSetting setting
= host_content_settings_map
->GetContentSetting(
219 CONTENT_SETTINGS_TYPE_NOTIFICATIONS
,
220 content_settings::ResourceIdentifier());
222 if (setting
== CONTENT_SETTING_ALLOW
)
223 return blink::WebNotificationPermissionAllowed
;
224 if (setting
== CONTENT_SETTING_BLOCK
)
225 return blink::WebNotificationPermissionDenied
;
227 return blink::WebNotificationPermissionDefault
;
230 void PlatformNotificationServiceImpl::DisplayNotification(
231 BrowserContext
* browser_context
,
233 const SkBitmap
& icon
,
234 const content::PlatformNotificationData
& notification_data
,
235 scoped_ptr
<content::DesktopNotificationDelegate
> delegate
,
236 base::Closure
* cancel_callback
) {
237 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
239 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
242 NotificationObjectProxy
* proxy
= new NotificationObjectProxy(delegate
.Pass());
243 Notification notification
= CreateNotificationFromData(
244 profile
, origin
, icon
, notification_data
, proxy
);
246 GetNotificationUIManager()->Add(notification
, profile
);
249 base::Bind(&CancelNotification
,
250 notification
.delegate_id(),
251 NotificationUIManager::GetProfileID(profile
));
253 profile
->GetHostContentSettingsMap()->UpdateLastUsage(
254 origin
, origin
, CONTENT_SETTINGS_TYPE_NOTIFICATIONS
);
257 void PlatformNotificationServiceImpl::DisplayPersistentNotification(
258 BrowserContext
* browser_context
,
259 int64_t persistent_notification_id
,
261 const SkBitmap
& icon
,
262 const content::PlatformNotificationData
& notification_data
) {
263 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
265 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
268 PersistentNotificationDelegate
* delegate
= new PersistentNotificationDelegate(
269 browser_context
, persistent_notification_id
, origin
);
271 Notification notification
= CreateNotificationFromData(
272 profile
, origin
, icon
, notification_data
, delegate
);
274 // TODO(peter): Remove this mapping when we have reliable id generation for
275 // the message_center::Notification objects.
276 persistent_notifications_
[persistent_notification_id
] = notification
.id();
278 GetNotificationUIManager()->Add(notification
, profile
);
280 profile
->GetHostContentSettingsMap()->UpdateLastUsage(
281 origin
, origin
, CONTENT_SETTINGS_TYPE_NOTIFICATIONS
);
284 void PlatformNotificationServiceImpl::ClosePersistentNotification(
285 BrowserContext
* browser_context
,
286 int64_t persistent_notification_id
) {
287 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
289 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
292 #if defined(OS_ANDROID)
293 // TODO(peter): Remove this conversion when the notification ids are being
294 // generated by the caller of this method.
295 std::string textual_persistent_notification_id
=
296 base::Int64ToString(persistent_notification_id
);
297 GetNotificationUIManager()->CancelById(
298 textual_persistent_notification_id
,
299 NotificationUIManager::GetProfileID(profile
));
301 auto iter
= persistent_notifications_
.find(persistent_notification_id
);
302 if (iter
== persistent_notifications_
.end())
305 GetNotificationUIManager()->CancelById(
306 iter
->second
, NotificationUIManager::GetProfileID(profile
));
308 persistent_notifications_
.erase(iter
);
312 bool PlatformNotificationServiceImpl::GetDisplayedPersistentNotifications(
313 BrowserContext
* browser_context
,
314 std::set
<std::string
>* displayed_notifications
) {
315 DCHECK(displayed_notifications
);
317 #if !defined(OS_ANDROID)
318 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
319 if (!profile
|| profile
->AsTestingProfile())
320 return false; // Tests will not have a message center.
322 // TODO(peter): Filter for persistent notifications only.
323 *displayed_notifications
=
324 GetNotificationUIManager()->GetAllIdsByProfile(profile
);
328 // Android cannot reliably return the notifications that are currently being
329 // displayed on the platform, see the comment in NotificationUIManagerAndroid.
331 #endif // !defined(OS_ANDROID)
334 Notification
PlatformNotificationServiceImpl::CreateNotificationFromData(
337 const SkBitmap
& icon
,
338 const content::PlatformNotificationData
& notification_data
,
339 NotificationDelegate
* delegate
) const {
340 base::string16 display_source
= DisplayNameForOrigin(profile
, origin
);
342 // TODO(peter): Icons for Web Notifications are currently always requested for
343 // 1x scale, whereas the displays on which they can be displayed can have a
344 // different pixel density. Be smarter about this when the API gets updated
345 // with a way for developers to specify images of different resolutions.
346 Notification
notification(origin
, notification_data
.title
,
347 notification_data
.body
, gfx::Image::CreateFrom1xBitmap(icon
),
348 display_source
, notification_data
.tag
, delegate
);
350 notification
.set_context_message(display_source
);
351 notification
.set_vibration_pattern(notification_data
.vibration_pattern
);
352 notification
.set_silent(notification_data
.silent
);
354 // Web Notifications do not timeout.
355 notification
.set_never_timeout(true);
360 NotificationUIManager
*
361 PlatformNotificationServiceImpl::GetNotificationUIManager() const {
362 if (notification_ui_manager_for_tests_
)
363 return notification_ui_manager_for_tests_
;
365 return g_browser_process
->notification_ui_manager();
368 void PlatformNotificationServiceImpl::SetNotificationUIManagerForTesting(
369 NotificationUIManager
* manager
) {
370 notification_ui_manager_for_tests_
= manager
;
373 base::string16
PlatformNotificationServiceImpl::DisplayNameForOrigin(
375 const GURL
& origin
) const {
376 #if defined(ENABLE_EXTENSIONS)
377 // If the source is an extension, lookup the display name.
378 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
379 const extensions::Extension
* extension
=
380 extensions::ExtensionRegistry::Get(profile
)->GetExtensionById(
381 origin
.host(), extensions::ExtensionRegistry::EVERYTHING
);
384 return base::UTF8ToUTF16(extension
->name());
388 std::string languages
=
389 profile
->GetPrefs()->GetString(prefs::kAcceptLanguages
);
391 return WebOriginDisplayName(origin
, languages
);
395 base::string16
PlatformNotificationServiceImpl::WebOriginDisplayName(
397 const std::string
& languages
) {
398 if (origin
.SchemeIsHTTPOrHTTPS()) {
399 base::string16 formatted_origin
;
400 if (origin
.SchemeIs(url::kHttpScheme
)) {
401 const url::Parsed
& parsed
= origin
.parsed_for_possibly_invalid_spec();
402 const std::string
& spec
= origin
.possibly_invalid_spec();
403 formatted_origin
.append(
406 parsed
.CountCharactersBefore(url::Parsed::USERNAME
, true));
408 formatted_origin
.append(net::IDNToUnicode(origin
.host(), languages
));
409 if (origin
.has_port()) {
410 formatted_origin
.push_back(':');
411 formatted_origin
.append(base::UTF8ToUTF16(origin
.port()));
413 return formatted_origin
;
416 // TODO(dewittj): Once file:// URLs are passed in to the origin
417 // GURL here, begin returning the path as the display name.
418 return net::FormatUrl(origin
, languages
);