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 "components/url_formatter/url_formatter.h"
21 #include "content/public/browser/browser_thread.h"
22 #include "content/public/browser/desktop_notification_delegate.h"
23 #include "content/public/browser/notification_event_dispatcher.h"
24 #include "content/public/browser/platform_notification_context.h"
25 #include "content/public/browser/storage_partition.h"
26 #include "content/public/common/platform_notification_data.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/notifier_state_tracker.h"
32 #include "chrome/browser/notifications/notifier_state_tracker_factory.h"
33 #include "extensions/browser/extension_registry.h"
34 #include "extensions/browser/info_map.h"
35 #include "extensions/common/constants.h"
36 #include "extensions/common/permissions/api_permission.h"
37 #include "extensions/common/permissions/permissions_data.h"
40 #if defined(OS_ANDROID)
41 #include "base/strings/string_number_conversions.h"
44 using content::BrowserContext
;
45 using content::BrowserThread
;
46 using content::PlatformNotificationContext
;
47 using message_center::NotifierId
;
51 // Callback to provide when deleting the data associated with persistent Web
52 // Notifications from the notification database.
53 void OnPersistentNotificationDataDeleted(bool success
) {
54 UMA_HISTOGRAM_BOOLEAN("Notifications.PersistentNotificationDataDeleted",
58 // Persistent notifications fired through the delegate do not care about the
59 // lifetime of the Service Worker responsible for executing the event.
60 void OnEventDispatchComplete(content::PersistentNotificationStatus status
) {
61 UMA_HISTOGRAM_ENUMERATION(
62 "Notifications.PersistentWebNotificationClickResult", status
,
63 content::PersistentNotificationStatus::
64 PERSISTENT_NOTIFICATION_STATUS_MAX
);
67 void CancelNotification(const std::string
& id
, ProfileID profile_id
) {
68 PlatformNotificationServiceImpl::GetInstance()
69 ->GetNotificationUIManager()->CancelById(id
, profile_id
);
75 PlatformNotificationServiceImpl
*
76 PlatformNotificationServiceImpl::GetInstance() {
77 return Singleton
<PlatformNotificationServiceImpl
>::get();
80 PlatformNotificationServiceImpl::PlatformNotificationServiceImpl()
81 : notification_ui_manager_for_tests_(nullptr) {}
83 PlatformNotificationServiceImpl::~PlatformNotificationServiceImpl() {}
85 void PlatformNotificationServiceImpl::OnPersistentNotificationClick(
86 BrowserContext
* browser_context
,
87 int64_t persistent_notification_id
,
89 int action_index
) const {
90 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
91 content::NotificationEventDispatcher::GetInstance()
92 ->DispatchNotificationClickEvent(
94 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 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
134 extensions::ExtensionRegistry
* registry
=
135 extensions::ExtensionRegistry::Get(browser_context
);
136 extensions::ProcessMap
* process_map
=
137 extensions::ProcessMap::Get(browser_context
);
139 const extensions::Extension
* extension
=
140 registry
->GetExtensionById(origin
.host(),
141 extensions::ExtensionRegistry::ENABLED
);
144 extension
->permissions_data()->HasAPIPermission(
145 extensions::APIPermission::kNotifications
) &&
146 process_map
->Contains(extension
->id(), render_process_id
)) {
147 NotifierStateTracker
* notifier_state_tracker
=
148 NotifierStateTrackerFactory::GetForProfile(profile
);
149 DCHECK(notifier_state_tracker
);
151 NotifierId
notifier_id(NotifierId::APPLICATION
, extension
->id());
152 if (notifier_state_tracker
->IsNotifierEnabled(notifier_id
))
153 return blink::WebNotificationPermissionAllowed
;
158 ContentSetting setting
=
159 DesktopNotificationProfileUtil::GetContentSetting(profile
, origin
);
161 if (setting
== CONTENT_SETTING_ALLOW
)
162 return blink::WebNotificationPermissionAllowed
;
163 if (setting
== CONTENT_SETTING_BLOCK
)
164 return blink::WebNotificationPermissionDenied
;
166 return blink::WebNotificationPermissionDefault
;
169 blink::WebNotificationPermission
170 PlatformNotificationServiceImpl::CheckPermissionOnIOThread(
171 content::ResourceContext
* resource_context
,
173 int render_process_id
) {
174 DCHECK_CURRENTLY_ON(BrowserThread::IO
);
176 ProfileIOData
* io_data
= ProfileIOData::FromResourceContext(resource_context
);
177 #if defined(ENABLE_EXTENSIONS)
178 // Extensions support an API permission named "notification". This will grant
179 // not only grant permission for using the Chrome App extension API, but also
180 // for the Web Notification API.
181 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
182 extensions::InfoMap
* extension_info_map
= io_data
->GetExtensionInfoMap();
183 const extensions::ProcessMap
& process_map
=
184 extension_info_map
->process_map();
186 const extensions::Extension
* extension
=
187 extension_info_map
->extensions().GetByID(origin
.host());
190 extension
->permissions_data()->HasAPIPermission(
191 extensions::APIPermission::kNotifications
) &&
192 process_map
.Contains(extension
->id(), render_process_id
)) {
193 if (!extension_info_map
->AreNotificationsDisabled(extension
->id()))
194 return blink::WebNotificationPermissionAllowed
;
199 // No enabled extensions exist, so check the normal host content settings.
200 HostContentSettingsMap
* host_content_settings_map
=
201 io_data
->GetHostContentSettingsMap();
202 ContentSetting setting
= host_content_settings_map
->GetContentSetting(
205 CONTENT_SETTINGS_TYPE_NOTIFICATIONS
,
206 content_settings::ResourceIdentifier());
208 if (setting
== CONTENT_SETTING_ALLOW
)
209 return blink::WebNotificationPermissionAllowed
;
210 if (setting
== CONTENT_SETTING_BLOCK
)
211 return blink::WebNotificationPermissionDenied
;
213 return blink::WebNotificationPermissionDefault
;
216 void PlatformNotificationServiceImpl::DisplayNotification(
217 BrowserContext
* browser_context
,
219 const SkBitmap
& icon
,
220 const content::PlatformNotificationData
& notification_data
,
221 scoped_ptr
<content::DesktopNotificationDelegate
> delegate
,
222 base::Closure
* cancel_callback
) {
223 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
225 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
228 NotificationObjectProxy
* proxy
= new NotificationObjectProxy(delegate
.Pass());
229 Notification notification
= CreateNotificationFromData(
230 profile
, origin
, icon
, notification_data
, proxy
);
232 GetNotificationUIManager()->Add(notification
, profile
);
235 base::Bind(&CancelNotification
,
236 notification
.delegate_id(),
237 NotificationUIManager::GetProfileID(profile
));
239 profile
->GetHostContentSettingsMap()->UpdateLastUsage(
240 origin
, origin
, CONTENT_SETTINGS_TYPE_NOTIFICATIONS
);
243 void PlatformNotificationServiceImpl::DisplayPersistentNotification(
244 BrowserContext
* browser_context
,
245 int64_t persistent_notification_id
,
247 const SkBitmap
& icon
,
248 const content::PlatformNotificationData
& notification_data
) {
249 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
251 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
254 PersistentNotificationDelegate
* delegate
= new PersistentNotificationDelegate(
255 browser_context
, persistent_notification_id
, origin
);
257 Notification notification
= CreateNotificationFromData(
258 profile
, origin
, icon
, notification_data
, delegate
);
260 // TODO(peter): Remove this mapping when we have reliable id generation for
261 // the message_center::Notification objects.
262 persistent_notifications_
[persistent_notification_id
] = notification
.id();
264 GetNotificationUIManager()->Add(notification
, profile
);
266 profile
->GetHostContentSettingsMap()->UpdateLastUsage(
267 origin
, origin
, CONTENT_SETTINGS_TYPE_NOTIFICATIONS
);
270 void PlatformNotificationServiceImpl::ClosePersistentNotification(
271 BrowserContext
* browser_context
,
272 int64_t persistent_notification_id
) {
273 DCHECK_CURRENTLY_ON(BrowserThread::UI
);
275 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
278 #if defined(OS_ANDROID)
279 // TODO(peter): Remove this conversion when the notification ids are being
280 // generated by the caller of this method.
281 std::string textual_persistent_notification_id
=
282 base::Int64ToString(persistent_notification_id
);
283 GetNotificationUIManager()->CancelById(
284 textual_persistent_notification_id
,
285 NotificationUIManager::GetProfileID(profile
));
287 auto iter
= persistent_notifications_
.find(persistent_notification_id
);
288 if (iter
== persistent_notifications_
.end())
291 GetNotificationUIManager()->CancelById(
292 iter
->second
, NotificationUIManager::GetProfileID(profile
));
294 persistent_notifications_
.erase(iter
);
298 bool PlatformNotificationServiceImpl::GetDisplayedPersistentNotifications(
299 BrowserContext
* browser_context
,
300 std::set
<std::string
>* displayed_notifications
) {
301 DCHECK(displayed_notifications
);
303 #if !defined(OS_ANDROID)
304 Profile
* profile
= Profile::FromBrowserContext(browser_context
);
305 if (!profile
|| profile
->AsTestingProfile())
306 return false; // Tests will not have a message center.
308 // TODO(peter): Filter for persistent notifications only.
309 *displayed_notifications
= GetNotificationUIManager()->GetAllIdsByProfile(
310 NotificationUIManager::GetProfileID(profile
));
314 // Android cannot reliably return the notifications that are currently being
315 // displayed on the platform, see the comment in NotificationUIManagerAndroid.
317 #endif // !defined(OS_ANDROID)
320 Notification
PlatformNotificationServiceImpl::CreateNotificationFromData(
323 const SkBitmap
& icon
,
324 const content::PlatformNotificationData
& notification_data
,
325 NotificationDelegate
* delegate
) const {
326 base::string16 display_source
= DisplayNameForOrigin(profile
, origin
);
328 // TODO(peter): Icons for Web Notifications are currently always requested for
329 // 1x scale, whereas the displays on which they can be displayed can have a
330 // different pixel density. Be smarter about this when the API gets updated
331 // with a way for developers to specify images of different resolutions.
332 Notification
notification(origin
, notification_data
.title
,
333 notification_data
.body
, gfx::Image::CreateFrom1xBitmap(icon
),
334 display_source
, notification_data
.tag
, delegate
);
336 notification
.set_context_message(display_source
);
337 notification
.set_vibration_pattern(notification_data
.vibration_pattern
);
338 notification
.set_silent(notification_data
.silent
);
340 std::vector
<message_center::ButtonInfo
> buttons
;
341 for (const auto& action
: notification_data
.actions
)
342 buttons
.push_back(message_center::ButtonInfo(action
.title
));
344 notification
.set_buttons(buttons
);
346 // Web Notifications do not timeout.
347 notification
.set_never_timeout(true);
352 NotificationUIManager
*
353 PlatformNotificationServiceImpl::GetNotificationUIManager() const {
354 if (notification_ui_manager_for_tests_
)
355 return notification_ui_manager_for_tests_
;
357 return g_browser_process
->notification_ui_manager();
360 void PlatformNotificationServiceImpl::SetNotificationUIManagerForTesting(
361 NotificationUIManager
* manager
) {
362 notification_ui_manager_for_tests_
= manager
;
365 base::string16
PlatformNotificationServiceImpl::DisplayNameForOrigin(
367 const GURL
& origin
) const {
368 #if defined(ENABLE_EXTENSIONS)
369 // If the source is an extension, lookup the display name.
370 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
371 const extensions::Extension
* extension
=
372 extensions::ExtensionRegistry::Get(profile
)->GetExtensionById(
373 origin
.host(), extensions::ExtensionRegistry::EVERYTHING
);
376 return base::UTF8ToUTF16(extension
->name());
380 std::string languages
=
381 profile
->GetPrefs()->GetString(prefs::kAcceptLanguages
);
383 return WebOriginDisplayName(origin
, languages
);
387 // TODO(palmer): It might be good to replace this with a call to
388 // |FormatUrlForSecurityDisplay|. crbug.com/496965
389 base::string16
PlatformNotificationServiceImpl::WebOriginDisplayName(
391 const std::string
& languages
) {
392 if (origin
.SchemeIsHTTPOrHTTPS()) {
393 base::string16 formatted_origin
;
394 if (origin
.SchemeIs(url::kHttpScheme
)) {
395 const url::Parsed
& parsed
= origin
.parsed_for_possibly_invalid_spec();
396 const std::string
& spec
= origin
.possibly_invalid_spec();
397 formatted_origin
.append(
400 parsed
.CountCharactersBefore(url::Parsed::USERNAME
, true));
402 formatted_origin
.append(
403 url_formatter::IDNToUnicode(origin
.host(), languages
));
404 if (origin
.has_port()) {
405 formatted_origin
.push_back(':');
406 formatted_origin
.append(base::UTF8ToUTF16(origin
.port()));
408 return formatted_origin
;
411 // TODO(dewittj): Once file:// URLs are passed in to the origin
412 // GURL here, begin returning the path as the display name.
413 return url_formatter::FormatUrl(origin
, languages
);