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/chromium_strings.h"
38 #include "grit/generated_resources.h"
39 #include "grit/theme_resources.h"
40 #include "net/base/escape.h"
41 #include "ui/base/resource/resource_bundle.h"
42 #include "ui/base/webui/web_ui_util.h"
43 #include "ui/message_center/notifier_settings.h"
45 #if defined(ENABLE_EXTENSIONS)
46 #include "chrome/browser/extensions/api/notifications/notifications_api.h"
47 #include "chrome/browser/extensions/extension_service.h"
48 #include "extensions/browser/event_router.h"
49 #include "extensions/browser/extension_registry.h"
50 #include "extensions/browser/extension_system.h"
51 #include "extensions/browser/extension_util.h"
52 #include "extensions/browser/info_map.h"
53 #include "extensions/common/constants.h"
54 #include "extensions/common/extension.h"
55 #include "extensions/common/extension_set.h"
58 using blink::WebTextDirection;
59 using content::BrowserThread;
60 using content::RenderViewHost;
61 using content::WebContents;
62 using message_center::NotifierId;
66 const char kChromeNowExtensionID[] = "pafkbggdmjlpgkdkcbjmhmfcdpncadgh";
68 void CancelNotification(const std::string& id) {
69 g_browser_process->notification_ui_manager()->CancelById(id);
75 // DesktopNotificationService -------------------------------------------------
78 void DesktopNotificationService::RegisterProfilePrefs(
79 user_prefs::PrefRegistrySyncable* registry) {
80 registry->RegisterListPref(
81 prefs::kMessageCenterDisabledExtensionIds,
82 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
83 registry->RegisterListPref(
84 prefs::kMessageCenterDisabledSystemComponentIds,
85 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
86 ExtensionWelcomeNotification::RegisterProfilePrefs(registry);
90 base::string16 DesktopNotificationService::CreateDataUrl(
92 const base::string16& title,
93 const base::string16& body,
94 WebTextDirection dir) {
96 std::vector<std::string> subst;
97 if (icon_url.is_valid()) {
98 resource = IDR_NOTIFICATION_ICON_HTML;
99 subst.push_back(icon_url.spec());
100 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title)));
101 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body)));
102 // icon float position
103 subst.push_back(dir == blink::WebTextDirectionRightToLeft ?
105 } else if (title.empty() || body.empty()) {
106 resource = IDR_NOTIFICATION_1LINE_HTML;
107 base::string16 line = title.empty() ? body : title;
108 // Strings are div names in the template file.
109 base::string16 line_name =
110 title.empty() ? base::ASCIIToUTF16("description")
111 : base::ASCIIToUTF16("title");
112 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line_name)));
113 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line)));
115 resource = IDR_NOTIFICATION_2LINE_HTML;
116 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title)));
117 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body)));
119 // body text direction
120 subst.push_back(dir == blink::WebTextDirectionRightToLeft ?
123 return CreateDataUrl(resource, subst);
127 base::string16 DesktopNotificationService::CreateDataUrl(
128 int resource, const std::vector<std::string>& subst) {
129 const base::StringPiece template_html(
130 ResourceBundle::GetSharedInstance().GetRawDataResource(
133 if (template_html.empty()) {
134 NOTREACHED() << "unable to load template. ID: " << resource;
135 return base::string16();
138 std::string data = ReplaceStringPlaceholders(template_html, subst, NULL);
139 return base::UTF8ToUTF16("data:text/html;charset=utf-8," +
140 net::EscapeQueryParamValue(data, false));
144 std::string DesktopNotificationService::AddIconNotification(
145 const GURL& origin_url,
146 const base::string16& title,
147 const base::string16& message,
148 const gfx::Image& icon,
149 const base::string16& replace_id,
150 NotificationDelegate* delegate,
152 Notification notification(origin_url, icon, title, message,
153 blink::WebTextDirectionDefault,
154 base::string16(), replace_id, delegate);
155 g_browser_process->notification_ui_manager()->Add(notification, profile);
156 return notification.delegate_id();
159 DesktopNotificationService::DesktopNotificationService(
161 NotificationUIManager* ui_manager)
162 : PermissionContextBase(profile, CONTENT_SETTINGS_TYPE_NOTIFICATIONS),
164 ui_manager_(ui_manager),
165 extension_registry_observer_(this),
166 weak_factory_(this) {
167 OnStringListPrefChanged(
168 prefs::kMessageCenterDisabledExtensionIds, &disabled_extension_ids_);
169 OnStringListPrefChanged(
170 prefs::kMessageCenterDisabledSystemComponentIds,
171 &disabled_system_component_ids_);
172 disabled_extension_id_pref_.Init(
173 prefs::kMessageCenterDisabledExtensionIds,
174 profile_->GetPrefs(),
176 &DesktopNotificationService::OnStringListPrefChanged,
177 base::Unretained(this),
178 base::Unretained(prefs::kMessageCenterDisabledExtensionIds),
179 base::Unretained(&disabled_extension_ids_)));
180 disabled_system_component_id_pref_.Init(
181 prefs::kMessageCenterDisabledSystemComponentIds,
182 profile_->GetPrefs(),
184 &DesktopNotificationService::OnStringListPrefChanged,
185 base::Unretained(this),
186 base::Unretained(prefs::kMessageCenterDisabledSystemComponentIds),
187 base::Unretained(&disabled_system_component_ids_)));
188 extension_registry_observer_.Add(
189 extensions::ExtensionRegistry::Get(profile_));
192 DesktopNotificationService::~DesktopNotificationService() {
195 void DesktopNotificationService::RequestNotificationPermission(
196 content::WebContents* web_contents,
197 const PermissionRequestID& request_id,
198 const GURL& requesting_frame,
200 const NotificationPermissionCallback& callback) {
201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
207 base::Bind(&DesktopNotificationService::OnNotificationPermissionRequested,
208 weak_factory_.GetWeakPtr(),
212 void DesktopNotificationService::ShowDesktopNotification(
213 const content::ShowDesktopNotificationHostMsgParams& params,
214 content::RenderFrameHost* render_frame_host,
215 scoped_ptr<content::DesktopNotificationDelegate> delegate,
216 base::Closure* cancel_callback) {
217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
218 const GURL& origin = params.origin;
219 NotificationObjectProxy* proxy =
220 new NotificationObjectProxy(render_frame_host, delegate.Pass());
222 base::string16 display_source = DisplayNameForOriginInProcessId(
223 origin, render_frame_host->GetProcess()->GetID());
224 Notification notification(origin, params.icon_url, params.title,
225 params.body, params.direction, display_source, params.replace_id,
228 // The webkit notification doesn't timeout.
229 notification.set_never_timeout(true);
231 GetUIManager()->Add(notification, profile_);
233 *cancel_callback = base::Bind(&CancelNotification, proxy->id());
235 DesktopNotificationProfileUtil::UsePermission(profile_, origin);
238 base::string16 DesktopNotificationService::DisplayNameForOriginInProcessId(
239 const GURL& origin, int process_id) {
240 #if defined(ENABLE_EXTENSIONS)
241 // If the source is an extension, lookup the display name.
242 if (origin.SchemeIs(extensions::kExtensionScheme)) {
243 extensions::InfoMap* extension_info_map =
244 extensions::ExtensionSystem::Get(profile_)->info_map();
245 if (extension_info_map) {
246 extensions::ExtensionSet extensions;
247 extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
250 extensions::APIPermission::kNotifications,
252 for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
253 iter != extensions.end(); ++iter) {
254 NotifierId notifier_id(NotifierId::APPLICATION, (*iter)->id());
255 if (IsNotifierEnabled(notifier_id))
256 return base::UTF8ToUTF16((*iter)->name());
262 return base::UTF8ToUTF16(origin.host());
265 NotificationUIManager* DesktopNotificationService::GetUIManager() {
266 // We defer setting ui_manager_ to the global singleton until we need it
267 // in order to avoid UI dependent construction during startup.
269 ui_manager_ = g_browser_process->notification_ui_manager();
273 bool DesktopNotificationService::IsNotifierEnabled(
274 const NotifierId& notifier_id) {
275 switch (notifier_id.type) {
276 case NotifierId::APPLICATION:
277 return disabled_extension_ids_.find(notifier_id.id) ==
278 disabled_extension_ids_.end();
279 case NotifierId::WEB_PAGE:
280 return DesktopNotificationProfileUtil::GetContentSetting(
281 profile_, notifier_id.url) == CONTENT_SETTING_ALLOW;
282 case NotifierId::SYSTEM_COMPONENT:
283 #if defined(OS_CHROMEOS)
284 return disabled_system_component_ids_.find(notifier_id.id) ==
285 disabled_system_component_ids_.end();
287 // We do not disable system component notifications.
296 void DesktopNotificationService::SetNotifierEnabled(
297 const NotifierId& notifier_id,
299 DCHECK_NE(NotifierId::WEB_PAGE, notifier_id.type);
301 bool add_new_item = false;
302 const char* pref_name = NULL;
303 scoped_ptr<base::StringValue> id;
304 switch (notifier_id.type) {
305 case NotifierId::APPLICATION:
306 pref_name = prefs::kMessageCenterDisabledExtensionIds;
307 add_new_item = !enabled;
308 id.reset(new base::StringValue(notifier_id.id));
309 FirePermissionLevelChangedEvent(notifier_id, enabled);
311 case NotifierId::SYSTEM_COMPONENT:
312 #if defined(OS_CHROMEOS)
313 pref_name = prefs::kMessageCenterDisabledSystemComponentIds;
314 add_new_item = !enabled;
315 id.reset(new base::StringValue(notifier_id.id));
323 DCHECK(pref_name != NULL);
325 ListPrefUpdate update(profile_->GetPrefs(), pref_name);
326 base::ListValue* const list = update.Get();
328 // AppendIfNotPresent will delete |adding_value| when the same value
330 list->AppendIfNotPresent(id.release());
332 list->Remove(*id, NULL);
336 void DesktopNotificationService::ShowWelcomeNotificationIfNecessary(
337 const Notification& notification) {
338 if (!chrome_now_welcome_notification_) {
339 chrome_now_welcome_notification_ =
340 ExtensionWelcomeNotification::Create(kChromeNowExtensionID, profile_);
343 if (chrome_now_welcome_notification_) {
344 chrome_now_welcome_notification_->ShowWelcomeNotificationIfNecessary(
349 void DesktopNotificationService::OnStringListPrefChanged(
350 const char* pref_name, std::set<std::string>* ids_field) {
352 // Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320
353 const PrefService* pref_service = profile_->GetPrefs();
355 const base::ListValue* pref_list = pref_service->GetList(pref_name);
356 for (size_t i = 0; i < pref_list->GetSize(); ++i) {
358 if (pref_list->GetString(i, &element) && !element.empty())
359 ids_field->insert(element);
361 LOG(WARNING) << i << "-th element is not a string for " << pref_name;
365 void DesktopNotificationService::OnExtensionUninstalled(
366 content::BrowserContext* browser_context,
367 const extensions::Extension* extension,
368 extensions::UninstallReason reason) {
369 #if defined(ENABLE_EXTENSIONS)
370 NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
371 if (IsNotifierEnabled(notifier_id))
374 // The settings for ephemeral apps will be persisted across cache evictions.
375 if (extensions::util::IsEphemeralApp(extension->id(), profile_))
378 SetNotifierEnabled(notifier_id, true);
382 // Unlike other permission types, granting a notification for a given origin
383 // will not take into account the |embedder_origin|, it will only be based
384 // on the requesting iframe origin.
385 // TODO(mukai) Consider why notifications behave differently than
386 // other permissions. crbug.com/416894
387 void DesktopNotificationService::UpdateContentSetting(
388 const GURL& requesting_origin,
389 const GURL& embedder_origin,
392 DesktopNotificationProfileUtil::GrantPermission(
393 profile_, requesting_origin);
395 DesktopNotificationProfileUtil::DenyPermission(profile_, requesting_origin);
399 void DesktopNotificationService::OnNotificationPermissionRequested(
400 const NotificationPermissionCallback& callback, bool allowed) {
401 blink::WebNotificationPermission permission = allowed ?
402 blink::WebNotificationPermissionAllowed :
403 blink::WebNotificationPermissionDenied;
405 callback.Run(permission);
408 void DesktopNotificationService::FirePermissionLevelChangedEvent(
409 const NotifierId& notifier_id, bool enabled) {
410 #if defined(ENABLE_EXTENSIONS)
411 DCHECK_EQ(NotifierId::APPLICATION, notifier_id.type);
412 extensions::api::notifications::PermissionLevel permission =
413 enabled ? extensions::api::notifications::PERMISSION_LEVEL_GRANTED
414 : extensions::api::notifications::PERMISSION_LEVEL_DENIED;
415 scoped_ptr<base::ListValue> args(new base::ListValue());
416 args->Append(new base::StringValue(
417 extensions::api::notifications::ToString(permission)));
418 scoped_ptr<extensions::Event> event(new extensions::Event(
419 extensions::api::notifications::OnPermissionLevelChanged::kEventName,
421 extensions::EventRouter::Get(profile_)
422 ->DispatchEventToExtension(notifier_id.id, event.Pass());
424 // Tell the IO thread that this extension's permission for notifications
426 extensions::InfoMap* extension_info_map =
427 extensions::ExtensionSystem::Get(profile_)->info_map();
428 BrowserThread::PostTask(
429 BrowserThread::IO, FROM_HERE,
430 base::Bind(&extensions::InfoMap::SetNotificationsDisabled,
431 extension_info_map, notifier_id.id, !enabled));