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/content_settings/content_settings_details.h"
15 #include "chrome/browser/content_settings/content_settings_provider.h"
16 #include "chrome/browser/notifications/desktop_notification_profile_util.h"
17 #include "chrome/browser/notifications/desktop_notification_service_factory.h"
18 #include "chrome/browser/notifications/notification.h"
19 #include "chrome/browser/notifications/notification_object_proxy.h"
20 #include "chrome/browser/notifications/notification_ui_manager.h"
21 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
22 #include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/ui/browser.h"
25 #include "chrome/common/pref_names.h"
26 #include "chrome/common/url_constants.h"
27 #include "components/pref_registry/pref_registry_syncable.h"
28 #include "content/public/browser/browser_thread.h"
29 #include "content/public/browser/desktop_notification_delegate.h"
30 #include "content/public/browser/notification_service.h"
31 #include "content/public/browser/render_frame_host.h"
32 #include "content/public/browser/render_process_host.h"
33 #include "content/public/browser/render_view_host.h"
34 #include "content/public/browser/web_contents.h"
35 #include "content/public/common/show_desktop_notification_params.h"
36 #include "grit/browser_resources.h"
37 #include "grit/theme_resources.h"
38 #include "net/base/escape.h"
39 #include "ui/base/resource/resource_bundle.h"
40 #include "ui/base/webui/web_ui_util.h"
41 #include "ui/message_center/notifier_settings.h"
43 #if defined(ENABLE_EXTENSIONS)
44 #include "chrome/browser/extensions/api/notifications/notifications_api.h"
45 #include "chrome/browser/extensions/extension_service.h"
46 #include "extensions/browser/event_router.h"
47 #include "extensions/browser/extension_registry.h"
48 #include "extensions/browser/extension_system.h"
49 #include "extensions/browser/extension_util.h"
50 #include "extensions/browser/info_map.h"
51 #include "extensions/common/constants.h"
52 #include "extensions/common/extension.h"
53 #include "extensions/common/extension_set.h"
56 using blink::WebTextDirection
;
57 using content::BrowserThread
;
58 using content::RenderViewHost
;
59 using content::WebContents
;
60 using message_center::NotifierId
;
64 const char kChromeNowExtensionID
[] = "pafkbggdmjlpgkdkcbjmhmfcdpncadgh";
66 void CancelNotification(const std::string
& id
) {
67 g_browser_process
->notification_ui_manager()->CancelById(id
);
73 // DesktopNotificationService -------------------------------------------------
76 void DesktopNotificationService::RegisterProfilePrefs(
77 user_prefs::PrefRegistrySyncable
* registry
) {
78 registry
->RegisterListPref(
79 prefs::kMessageCenterDisabledExtensionIds
,
80 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
81 registry
->RegisterListPref(
82 prefs::kMessageCenterDisabledSystemComponentIds
,
83 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
84 ExtensionWelcomeNotification::RegisterProfilePrefs(registry
);
88 base::string16
DesktopNotificationService::CreateDataUrl(
90 const base::string16
& title
,
91 const base::string16
& body
,
92 WebTextDirection dir
) {
94 std::vector
<std::string
> subst
;
95 if (icon_url
.is_valid()) {
96 resource
= IDR_NOTIFICATION_ICON_HTML
;
97 subst
.push_back(icon_url
.spec());
98 subst
.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title
)));
99 subst
.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body
)));
100 // icon float position
101 subst
.push_back(dir
== blink::WebTextDirectionRightToLeft
?
103 } else if (title
.empty() || body
.empty()) {
104 resource
= IDR_NOTIFICATION_1LINE_HTML
;
105 base::string16 line
= title
.empty() ? body
: title
;
106 // Strings are div names in the template file.
107 base::string16 line_name
=
108 title
.empty() ? base::ASCIIToUTF16("description")
109 : base::ASCIIToUTF16("title");
110 subst
.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line_name
)));
111 subst
.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line
)));
113 resource
= IDR_NOTIFICATION_2LINE_HTML
;
114 subst
.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title
)));
115 subst
.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body
)));
117 // body text direction
118 subst
.push_back(dir
== blink::WebTextDirectionRightToLeft
?
121 return CreateDataUrl(resource
, subst
);
125 base::string16
DesktopNotificationService::CreateDataUrl(
126 int resource
, const std::vector
<std::string
>& subst
) {
127 const base::StringPiece
template_html(
128 ResourceBundle::GetSharedInstance().GetRawDataResource(
131 if (template_html
.empty()) {
132 NOTREACHED() << "unable to load template. ID: " << resource
;
133 return base::string16();
136 std::string data
= ReplaceStringPlaceholders(template_html
, subst
, NULL
);
137 return base::UTF8ToUTF16("data:text/html;charset=utf-8," +
138 net::EscapeQueryParamValue(data
, false));
142 std::string
DesktopNotificationService::AddIconNotification(
143 const GURL
& origin_url
,
144 const base::string16
& title
,
145 const base::string16
& message
,
146 const gfx::Image
& icon
,
147 const base::string16
& replace_id
,
148 NotificationDelegate
* delegate
,
150 Notification
notification(origin_url
, icon
, title
, message
,
151 blink::WebTextDirectionDefault
,
152 base::string16(), replace_id
, delegate
);
153 g_browser_process
->notification_ui_manager()->Add(notification
, profile
);
154 return notification
.delegate_id();
157 DesktopNotificationService::DesktopNotificationService(
159 NotificationUIManager
* ui_manager
)
160 : PermissionContextBase(profile
, CONTENT_SETTINGS_TYPE_NOTIFICATIONS
),
162 ui_manager_(ui_manager
),
163 extension_registry_observer_(this),
164 weak_factory_(this) {
165 OnStringListPrefChanged(
166 prefs::kMessageCenterDisabledExtensionIds
, &disabled_extension_ids_
);
167 OnStringListPrefChanged(
168 prefs::kMessageCenterDisabledSystemComponentIds
,
169 &disabled_system_component_ids_
);
170 disabled_extension_id_pref_
.Init(
171 prefs::kMessageCenterDisabledExtensionIds
,
172 profile_
->GetPrefs(),
174 &DesktopNotificationService::OnStringListPrefChanged
,
175 base::Unretained(this),
176 base::Unretained(prefs::kMessageCenterDisabledExtensionIds
),
177 base::Unretained(&disabled_extension_ids_
)));
178 disabled_system_component_id_pref_
.Init(
179 prefs::kMessageCenterDisabledSystemComponentIds
,
180 profile_
->GetPrefs(),
182 &DesktopNotificationService::OnStringListPrefChanged
,
183 base::Unretained(this),
184 base::Unretained(prefs::kMessageCenterDisabledSystemComponentIds
),
185 base::Unretained(&disabled_system_component_ids_
)));
186 extension_registry_observer_
.Add(
187 extensions::ExtensionRegistry::Get(profile_
));
190 DesktopNotificationService::~DesktopNotificationService() {
193 void DesktopNotificationService::RequestNotificationPermission(
194 content::WebContents
* web_contents
,
195 const PermissionRequestID
& request_id
,
196 const GURL
& requesting_frame
,
198 const NotificationPermissionCallback
& callback
) {
199 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
205 base::Bind(&DesktopNotificationService::OnNotificationPermissionRequested
,
206 weak_factory_
.GetWeakPtr(),
210 void DesktopNotificationService::ShowDesktopNotification(
211 const content::ShowDesktopNotificationHostMsgParams
& params
,
212 content::RenderFrameHost
* render_frame_host
,
213 scoped_ptr
<content::DesktopNotificationDelegate
> delegate
,
214 base::Closure
* cancel_callback
) {
215 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
216 const GURL
& origin
= params
.origin
;
217 NotificationObjectProxy
* proxy
=
218 new NotificationObjectProxy(render_frame_host
, delegate
.Pass());
220 base::string16 display_source
= DisplayNameForOriginInProcessId(
221 origin
, render_frame_host
->GetProcess()->GetID());
222 Notification
notification(origin
, params
.icon_url
, params
.title
,
223 params
.body
, params
.direction
, display_source
, params
.replace_id
,
226 // The webkit notification doesn't timeout.
227 notification
.set_never_timeout(true);
229 GetUIManager()->Add(notification
, profile_
);
231 *cancel_callback
= base::Bind(&CancelNotification
, proxy
->id());
233 DesktopNotificationProfileUtil::UsePermission(profile_
, origin
);
236 base::string16
DesktopNotificationService::DisplayNameForOriginInProcessId(
237 const GURL
& origin
, int process_id
) {
238 #if defined(ENABLE_EXTENSIONS)
239 // If the source is an extension, lookup the display name.
240 if (origin
.SchemeIs(extensions::kExtensionScheme
)) {
241 extensions::InfoMap
* extension_info_map
=
242 extensions::ExtensionSystem::Get(profile_
)->info_map();
243 if (extension_info_map
) {
244 extensions::ExtensionSet extensions
;
245 extension_info_map
->GetExtensionsWithAPIPermissionForSecurityOrigin(
248 extensions::APIPermission::kNotifications
,
250 for (extensions::ExtensionSet::const_iterator iter
= extensions
.begin();
251 iter
!= extensions
.end(); ++iter
) {
252 NotifierId
notifier_id(NotifierId::APPLICATION
, (*iter
)->id());
253 if (IsNotifierEnabled(notifier_id
))
254 return base::UTF8ToUTF16((*iter
)->name());
260 return base::UTF8ToUTF16(origin
.host());
263 NotificationUIManager
* DesktopNotificationService::GetUIManager() {
264 // We defer setting ui_manager_ to the global singleton until we need it
265 // in order to avoid UI dependent construction during startup.
267 ui_manager_
= g_browser_process
->notification_ui_manager();
271 bool DesktopNotificationService::IsNotifierEnabled(
272 const NotifierId
& notifier_id
) {
273 switch (notifier_id
.type
) {
274 case NotifierId::APPLICATION
:
275 return disabled_extension_ids_
.find(notifier_id
.id
) ==
276 disabled_extension_ids_
.end();
277 case NotifierId::WEB_PAGE
:
278 return DesktopNotificationProfileUtil::GetContentSetting(
279 profile_
, notifier_id
.url
) == CONTENT_SETTING_ALLOW
;
280 case NotifierId::SYSTEM_COMPONENT
:
281 #if defined(OS_CHROMEOS)
282 return disabled_system_component_ids_
.find(notifier_id
.id
) ==
283 disabled_system_component_ids_
.end();
285 // We do not disable system component notifications.
294 void DesktopNotificationService::SetNotifierEnabled(
295 const NotifierId
& notifier_id
,
297 DCHECK_NE(NotifierId::WEB_PAGE
, notifier_id
.type
);
299 bool add_new_item
= false;
300 const char* pref_name
= NULL
;
301 scoped_ptr
<base::StringValue
> id
;
302 switch (notifier_id
.type
) {
303 case NotifierId::APPLICATION
:
304 pref_name
= prefs::kMessageCenterDisabledExtensionIds
;
305 add_new_item
= !enabled
;
306 id
.reset(new base::StringValue(notifier_id
.id
));
307 FirePermissionLevelChangedEvent(notifier_id
, enabled
);
309 case NotifierId::SYSTEM_COMPONENT
:
310 #if defined(OS_CHROMEOS)
311 pref_name
= prefs::kMessageCenterDisabledSystemComponentIds
;
312 add_new_item
= !enabled
;
313 id
.reset(new base::StringValue(notifier_id
.id
));
321 DCHECK(pref_name
!= NULL
);
323 ListPrefUpdate
update(profile_
->GetPrefs(), pref_name
);
324 base::ListValue
* const list
= update
.Get();
326 // AppendIfNotPresent will delete |adding_value| when the same value
328 list
->AppendIfNotPresent(id
.release());
330 list
->Remove(*id
, NULL
);
334 void DesktopNotificationService::ShowWelcomeNotificationIfNecessary(
335 const Notification
& notification
) {
336 if (!chrome_now_welcome_notification_
) {
337 chrome_now_welcome_notification_
=
338 ExtensionWelcomeNotification::Create(kChromeNowExtensionID
, profile_
);
341 if (chrome_now_welcome_notification_
) {
342 chrome_now_welcome_notification_
->ShowWelcomeNotificationIfNecessary(
347 void DesktopNotificationService::OnStringListPrefChanged(
348 const char* pref_name
, std::set
<std::string
>* ids_field
) {
350 // Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320
351 const PrefService
* pref_service
= profile_
->GetPrefs();
353 const base::ListValue
* pref_list
= pref_service
->GetList(pref_name
);
354 for (size_t i
= 0; i
< pref_list
->GetSize(); ++i
) {
356 if (pref_list
->GetString(i
, &element
) && !element
.empty())
357 ids_field
->insert(element
);
359 LOG(WARNING
) << i
<< "-th element is not a string for " << pref_name
;
363 void DesktopNotificationService::OnExtensionUninstalled(
364 content::BrowserContext
* browser_context
,
365 const extensions::Extension
* extension
,
366 extensions::UninstallReason reason
) {
367 #if defined(ENABLE_EXTENSIONS)
368 NotifierId
notifier_id(NotifierId::APPLICATION
, extension
->id());
369 if (IsNotifierEnabled(notifier_id
))
372 // The settings for ephemeral apps will be persisted across cache evictions.
373 if (extensions::util::IsEphemeralApp(extension
->id(), profile_
))
376 SetNotifierEnabled(notifier_id
, true);
380 void DesktopNotificationService::OnNotificationPermissionRequested(
381 const NotificationPermissionCallback
& callback
, bool allowed
) {
382 blink::WebNotificationPermission permission
= allowed
?
383 blink::WebNotificationPermissionAllowed
:
384 blink::WebNotificationPermissionDenied
;
386 callback
.Run(permission
);
389 void DesktopNotificationService::FirePermissionLevelChangedEvent(
390 const NotifierId
& notifier_id
, bool enabled
) {
391 #if defined(ENABLE_EXTENSIONS)
392 DCHECK_EQ(NotifierId::APPLICATION
, notifier_id
.type
);
393 extensions::api::notifications::PermissionLevel permission
=
394 enabled
? extensions::api::notifications::PERMISSION_LEVEL_GRANTED
395 : extensions::api::notifications::PERMISSION_LEVEL_DENIED
;
396 scoped_ptr
<base::ListValue
> args(new base::ListValue());
397 args
->Append(new base::StringValue(
398 extensions::api::notifications::ToString(permission
)));
399 scoped_ptr
<extensions::Event
> event(new extensions::Event(
400 extensions::api::notifications::OnPermissionLevelChanged::kEventName
,
402 extensions::EventRouter::Get(profile_
)
403 ->DispatchEventToExtension(notifier_id
.id
, event
.Pass());
405 // Tell the IO thread that this extension's permission for notifications
407 extensions::InfoMap
* extension_info_map
=
408 extensions::ExtensionSystem::Get(profile_
)->info_map();
409 BrowserThread::PostTask(
410 BrowserThread::IO
, FROM_HERE
,
411 base::Bind(&extensions::InfoMap::SetNotificationsDisabled
,
412 extension_info_map
, notifier_id
.id
, !enabled
));