1 // Copyright (c) 2012 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/desktop_notification_service.h"
8 #include "base/metrics/histogram.h"
9 #include "base/prefs/scoped_user_pref_update.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/threading/thread.h"
12 #include "chrome/browser/browser_process.h"
13 #include "chrome/browser/chrome_notification_types.h"
14 #include "chrome/browser/notifications/desktop_notification_profile_util.h"
15 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
16 #include "chrome/browser/notifications/notification.h"
17 #include "chrome/browser/notifications/notification_object_proxy.h"
18 #include "chrome/browser/notifications/notification_ui_manager.h"
19 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
20 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
21 #include "chrome/browser/profiles/profile.h"
22 #include "chrome/browser/ui/browser.h"
23 #include "chrome/common/pref_names.h"
24 #include "chrome/common/url_constants.h"
25 #include "components/content_settings/core/common/permission_request_id.h"
26 #include "components/pref_registry/pref_registry_syncable.h"
27 #include "content/public/browser/browser_thread.h"
28 #include "content/public/browser/desktop_notification_delegate.h"
29 #include "content/public/browser/notification_service.h"
30 #include "content/public/browser/render_frame_host.h"
31 #include "content/public/browser/render_process_host.h"
32 #include "content/public/browser/render_view_host.h"
33 #include "content/public/browser/web_contents.h"
34 #include "content/public/common/show_desktop_notification_params.h"
35 #include "ui/base/webui/web_ui_util.h"
36 #include "ui/message_center/notifier_settings.h"
38 #if defined(ENABLE_EXTENSIONS)
39 #include "chrome/browser/extensions/api/notifications/notifications_api.h"
40 #include "chrome/browser/extensions/extension_service.h"
41 #include "extensions/browser/event_router.h"
42 #include "extensions/browser/extension_registry.h"
43 #include "extensions/browser/extension_system.h"
44 #include "extensions/browser/extension_util.h"
45 #include "extensions/browser/info_map.h"
46 #include "extensions/browser/suggest_permission_util.h"
47 #include "extensions/common/constants.h"
48 #include "extensions/common/extension.h"
49 #include "extensions/common/extension_set.h"
52 using blink::WebTextDirection
;
53 using content::BrowserThread
;
54 using content::RenderViewHost
;
55 using content::WebContents
;
56 using message_center::NotifierId
;
60 void CancelNotification(const std::string
& id
, ProfileID profile_id
) {
61 g_browser_process
->notification_ui_manager()->CancelById(id
, profile_id
);
66 // DesktopNotificationService -------------------------------------------------
69 void DesktopNotificationService::RegisterProfilePrefs(
70 user_prefs::PrefRegistrySyncable
* registry
) {
71 registry
->RegisterListPref(
72 prefs::kMessageCenterDisabledExtensionIds
,
73 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
74 registry
->RegisterListPref(
75 prefs::kMessageCenterDisabledSystemComponentIds
,
76 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
80 std::string
DesktopNotificationService::AddIconNotification(
81 const GURL
& origin_url
,
82 const base::string16
& title
,
83 const base::string16
& message
,
84 const gfx::Image
& icon
,
85 const base::string16
& replace_id
,
86 NotificationDelegate
* delegate
,
88 Notification
notification(message_center::NOTIFICATION_TYPE_SIMPLE
,
93 blink::WebTextDirectionDefault
,
94 message_center::NotifierId(origin_url
),
97 message_center::RichNotificationData(),
99 g_browser_process
->notification_ui_manager()->Add(notification
, profile
);
100 return notification
.delegate_id();
103 DesktopNotificationService::DesktopNotificationService(Profile
* profile
)
104 : PermissionContextBase(profile
, CONTENT_SETTINGS_TYPE_NOTIFICATIONS
),
106 #if defined(ENABLE_EXTENSIONS)
107 extension_registry_observer_(this),
109 weak_factory_(this) {
110 OnStringListPrefChanged(
111 prefs::kMessageCenterDisabledExtensionIds
, &disabled_extension_ids_
);
112 OnStringListPrefChanged(
113 prefs::kMessageCenterDisabledSystemComponentIds
,
114 &disabled_system_component_ids_
);
115 disabled_extension_id_pref_
.Init(
116 prefs::kMessageCenterDisabledExtensionIds
,
117 profile_
->GetPrefs(),
119 &DesktopNotificationService::OnStringListPrefChanged
,
120 base::Unretained(this),
121 base::Unretained(prefs::kMessageCenterDisabledExtensionIds
),
122 base::Unretained(&disabled_extension_ids_
)));
123 disabled_system_component_id_pref_
.Init(
124 prefs::kMessageCenterDisabledSystemComponentIds
,
125 profile_
->GetPrefs(),
127 &DesktopNotificationService::OnStringListPrefChanged
,
128 base::Unretained(this),
129 base::Unretained(prefs::kMessageCenterDisabledSystemComponentIds
),
130 base::Unretained(&disabled_system_component_ids_
)));
131 #if defined(ENABLE_EXTENSIONS)
132 extension_registry_observer_
.Add(
133 extensions::ExtensionRegistry::Get(profile_
));
137 DesktopNotificationService::~DesktopNotificationService() {
140 void DesktopNotificationService::RequestNotificationPermission(
141 content::WebContents
* web_contents
,
142 const PermissionRequestID
& request_id
,
143 const GURL
& requesting_origin
,
145 const NotificationPermissionCallback
& callback
) {
146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
148 #if defined(ENABLE_EXTENSIONS)
149 extensions::InfoMap
* extension_info_map
=
150 extensions::ExtensionSystem::Get(profile_
)->info_map();
151 const extensions::Extension
* extension
= NULL
;
152 if (extension_info_map
) {
153 extensions::ExtensionSet extensions
;
154 extension_info_map
->GetExtensionsWithAPIPermissionForSecurityOrigin(
156 request_id
.render_process_id(),
157 extensions::APIPermission::kNotifications
,
159 for (extensions::ExtensionSet::const_iterator iter
= extensions
.begin();
160 iter
!= extensions
.end(); ++iter
) {
161 if (IsNotifierEnabled(NotifierId(
162 NotifierId::APPLICATION
, (*iter
)->id()))) {
163 extension
= iter
->get();
168 if (IsExtensionWithPermissionOrSuggestInConsole(
169 extensions::APIPermission::kNotifications
,
171 web_contents
->GetRenderViewHost())) {
172 callback
.Run(blink::WebNotificationPermissionAllowed
);
182 base::Bind(&DesktopNotificationService::OnNotificationPermissionRequested
,
183 weak_factory_
.GetWeakPtr(),
187 void DesktopNotificationService::ShowDesktopNotification(
188 const content::ShowDesktopNotificationHostMsgParams
& params
,
189 content::RenderFrameHost
* render_frame_host
,
190 scoped_ptr
<content::DesktopNotificationDelegate
> delegate
,
191 base::Closure
* cancel_callback
) {
192 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
193 const GURL
& origin
= params
.origin
;
194 NotificationObjectProxy
* proxy
= new NotificationObjectProxy(delegate
.Pass());
196 base::string16 display_source
= DisplayNameForOriginInProcessId(
197 origin
, render_frame_host
->GetProcess()->GetID());
199 // TODO(peter): Icons for Web Notifications are currently always requested for
200 // 1x scale, whereas the displays on which they can be displayed can have a
201 // different pixel density. Be smarter about this when the API gets updated
202 // with a way for developers to specify images of different resolutions.
203 Notification
notification(origin
, params
.title
, params
.body
,
204 gfx::Image::CreateFrom1xBitmap(params
.icon
),
205 display_source
, params
.replace_id
, proxy
);
207 // The webkit notification doesn't timeout.
208 notification
.set_never_timeout(true);
210 g_browser_process
->notification_ui_manager()->Add(notification
, profile_
);
213 base::Bind(&CancelNotification
,
215 NotificationUIManager::GetProfileID(profile_
));
217 DesktopNotificationProfileUtil::UsePermission(profile_
, origin
);
220 base::string16
DesktopNotificationService::DisplayNameForOriginInProcessId(
221 const GURL
& origin
, int process_id
) {
222 #if defined(ENABLE_EXTENSIONS)
223 // If the source is an extension, lookup the display name.
224 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
225 extensions::InfoMap
* extension_info_map
=
226 extensions::ExtensionSystem::Get(profile_
)->info_map();
227 if (extension_info_map
) {
228 extensions::ExtensionSet extensions
;
229 extension_info_map
->GetExtensionsWithAPIPermissionForSecurityOrigin(
232 extensions::APIPermission::kNotifications
,
234 for (extensions::ExtensionSet::const_iterator iter
= extensions
.begin();
235 iter
!= extensions
.end(); ++iter
) {
236 NotifierId
notifier_id(NotifierId::APPLICATION
, (*iter
)->id());
237 if (IsNotifierEnabled(notifier_id
))
238 return base::UTF8ToUTF16((*iter
)->name());
244 return base::UTF8ToUTF16(origin
.host());
247 bool DesktopNotificationService::IsNotifierEnabled(
248 const NotifierId
& notifier_id
) {
249 switch (notifier_id
.type
) {
250 case NotifierId::APPLICATION
:
251 return disabled_extension_ids_
.find(notifier_id
.id
) ==
252 disabled_extension_ids_
.end();
253 case NotifierId::WEB_PAGE
:
254 return DesktopNotificationProfileUtil::GetContentSetting(
255 profile_
, notifier_id
.url
) == CONTENT_SETTING_ALLOW
;
256 case NotifierId::SYSTEM_COMPONENT
:
257 #if defined(OS_CHROMEOS)
258 return disabled_system_component_ids_
.find(notifier_id
.id
) ==
259 disabled_system_component_ids_
.end();
261 // We do not disable system component notifications.
270 void DesktopNotificationService::SetNotifierEnabled(
271 const NotifierId
& notifier_id
,
273 DCHECK_NE(NotifierId::WEB_PAGE
, notifier_id
.type
);
275 bool add_new_item
= false;
276 const char* pref_name
= NULL
;
277 scoped_ptr
<base::StringValue
> id
;
278 switch (notifier_id
.type
) {
279 case NotifierId::APPLICATION
:
280 pref_name
= prefs::kMessageCenterDisabledExtensionIds
;
281 add_new_item
= !enabled
;
282 id
.reset(new base::StringValue(notifier_id
.id
));
283 FirePermissionLevelChangedEvent(notifier_id
, enabled
);
285 case NotifierId::SYSTEM_COMPONENT
:
286 #if defined(OS_CHROMEOS)
287 pref_name
= prefs::kMessageCenterDisabledSystemComponentIds
;
288 add_new_item
= !enabled
;
289 id
.reset(new base::StringValue(notifier_id
.id
));
297 DCHECK(pref_name
!= NULL
);
299 ListPrefUpdate
update(profile_
->GetPrefs(), pref_name
);
300 base::ListValue
* const list
= update
.Get();
302 // AppendIfNotPresent will delete |adding_value| when the same value
304 list
->AppendIfNotPresent(id
.release());
306 list
->Remove(*id
, NULL
);
310 void DesktopNotificationService::OnStringListPrefChanged(
311 const char* pref_name
, std::set
<std::string
>* ids_field
) {
313 // Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320
314 const PrefService
* pref_service
= profile_
->GetPrefs();
316 const base::ListValue
* pref_list
= pref_service
->GetList(pref_name
);
317 for (size_t i
= 0; i
< pref_list
->GetSize(); ++i
) {
319 if (pref_list
->GetString(i
, &element
) && !element
.empty())
320 ids_field
->insert(element
);
322 LOG(WARNING
) << i
<< "-th element is not a string for " << pref_name
;
326 #if defined(ENABLE_EXTENSIONS)
327 void DesktopNotificationService::OnExtensionUninstalled(
328 content::BrowserContext
* browser_context
,
329 const extensions::Extension
* extension
,
330 extensions::UninstallReason reason
) {
331 NotifierId
notifier_id(NotifierId::APPLICATION
, extension
->id());
332 if (IsNotifierEnabled(notifier_id
))
335 // The settings for ephemeral apps will be persisted across cache evictions.
336 if (extensions::util::IsEphemeralApp(extension
->id(), profile_
))
339 SetNotifierEnabled(notifier_id
, true);
343 // Unlike other permission types, granting a notification for a given origin
344 // will not take into account the |embedder_origin|, it will only be based
345 // on the requesting iframe origin.
346 // TODO(mukai) Consider why notifications behave differently than
347 // other permissions. crbug.com/416894
348 void DesktopNotificationService::UpdateContentSetting(
349 const GURL
& requesting_origin
,
350 const GURL
& embedder_origin
,
353 DesktopNotificationProfileUtil::GrantPermission(
354 profile_
, requesting_origin
);
356 DesktopNotificationProfileUtil::DenyPermission(profile_
, requesting_origin
);
360 void DesktopNotificationService::OnNotificationPermissionRequested(
361 const NotificationPermissionCallback
& callback
, bool allowed
) {
362 blink::WebNotificationPermission permission
= allowed
?
363 blink::WebNotificationPermissionAllowed
:
364 blink::WebNotificationPermissionDenied
;
366 callback
.Run(permission
);
369 void DesktopNotificationService::FirePermissionLevelChangedEvent(
370 const NotifierId
& notifier_id
, bool enabled
) {
371 #if defined(ENABLE_EXTENSIONS)
372 DCHECK_EQ(NotifierId::APPLICATION
, notifier_id
.type
);
373 extensions::api::notifications::PermissionLevel permission
=
374 enabled
? extensions::api::notifications::PERMISSION_LEVEL_GRANTED
375 : extensions::api::notifications::PERMISSION_LEVEL_DENIED
;
376 scoped_ptr
<base::ListValue
> args(new base::ListValue());
377 args
->Append(new base::StringValue(
378 extensions::api::notifications::ToString(permission
)));
379 scoped_ptr
<extensions::Event
> event(new extensions::Event(
380 extensions::api::notifications::OnPermissionLevelChanged::kEventName
,
382 extensions::EventRouter::Get(profile_
)
383 ->DispatchEventToExtension(notifier_id
.id
, event
.Pass());
385 // Tell the IO thread that this extension's permission for notifications
387 extensions::InfoMap
* extension_info_map
=
388 extensions::ExtensionSystem::Get(profile_
)->info_map();
389 BrowserThread::PostTask(
390 BrowserThread::IO
, FROM_HERE
,
391 base::Bind(&extensions::InfoMap::SetNotificationsDisabled
,
392 extension_info_map
, notifier_id
.id
, !enabled
));