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.
4
5 #include "chrome/browser/notifications/desktop_notification_service.h"
6
7 #include "base/bind.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/pref_registry/pref_registry_syncable.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "content/public/browser/desktop_notification_delegate.h"
28 #include "content/public/browser/notification_service.h"
29 #include "content/public/browser/render_frame_host.h"
30 #include "content/public/browser/render_process_host.h"
31 #include "content/public/browser/render_view_host.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/common/show_desktop_notification_params.h"
34 #include "ui/base/webui/web_ui_util.h"
35 #include "ui/message_center/notifier_settings.h"
36
37 #if defined(ENABLE_EXTENSIONS)
38 #include "chrome/browser/extensions/api/notifications/notifications_api.h"
39 #include "chrome/browser/extensions/extension_service.h"
40 #include "extensions/browser/event_router.h"
41 #include "extensions/browser/extension_registry.h"
42 #include "extensions/browser/extension_system.h"
43 #include "extensions/browser/extension_util.h"
44 #include "extensions/browser/info_map.h"
45 #include "extensions/common/constants.h"
46 #include "extensions/common/extension.h"
47 #include "extensions/common/extension_set.h"
48 #endif
49
50 using blink::WebTextDirection;
51 using content::BrowserThread;
52 using content::RenderViewHost;
53 using content::WebContents;
54 using message_center::NotifierId;
55
56 namespace {
57
CancelNotification(const std::string & id)58 void CancelNotification(const std::string& id) {
59 g_browser_process->notification_ui_manager()->CancelById(id);
60 }
61
62 } // namespace
63
64 // DesktopNotificationService -------------------------------------------------
65
66 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)67 void DesktopNotificationService::RegisterProfilePrefs(
68 user_prefs::PrefRegistrySyncable* registry) {
69 registry->RegisterListPref(
70 prefs::kMessageCenterDisabledExtensionIds,
71 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
72 registry->RegisterListPref(
73 prefs::kMessageCenterDisabledSystemComponentIds,
74 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
75 }
76
77 // static
AddIconNotification(const GURL & origin_url,const base::string16 & title,const base::string16 & message,const gfx::Image & icon,const base::string16 & replace_id,NotificationDelegate * delegate,Profile * profile)78 std::string DesktopNotificationService::AddIconNotification(
79 const GURL& origin_url,
80 const base::string16& title,
81 const base::string16& message,
82 const gfx::Image& icon,
83 const base::string16& replace_id,
84 NotificationDelegate* delegate,
85 Profile* profile) {
86 Notification notification(message_center::NOTIFICATION_TYPE_SIMPLE,
87 origin_url,
88 title,
89 message,
90 icon,
91 blink::WebTextDirectionDefault,
92 message_center::NotifierId(origin_url),
93 base::string16(),
94 replace_id,
95 message_center::RichNotificationData(),
96 delegate);
97 g_browser_process->notification_ui_manager()->Add(notification, profile);
98 return notification.delegate_id();
99 }
100
DesktopNotificationService(Profile * profile)101 DesktopNotificationService::DesktopNotificationService(Profile* profile)
102 : PermissionContextBase(profile, CONTENT_SETTINGS_TYPE_NOTIFICATIONS),
103 profile_(profile),
104 #if defined(ENABLE_EXTENSIONS)
105 extension_registry_observer_(this),
106 #endif
107 weak_factory_(this) {
108 OnStringListPrefChanged(
109 prefs::kMessageCenterDisabledExtensionIds, &disabled_extension_ids_);
110 OnStringListPrefChanged(
111 prefs::kMessageCenterDisabledSystemComponentIds,
112 &disabled_system_component_ids_);
113 disabled_extension_id_pref_.Init(
114 prefs::kMessageCenterDisabledExtensionIds,
115 profile_->GetPrefs(),
116 base::Bind(
117 &DesktopNotificationService::OnStringListPrefChanged,
118 base::Unretained(this),
119 base::Unretained(prefs::kMessageCenterDisabledExtensionIds),
120 base::Unretained(&disabled_extension_ids_)));
121 disabled_system_component_id_pref_.Init(
122 prefs::kMessageCenterDisabledSystemComponentIds,
123 profile_->GetPrefs(),
124 base::Bind(
125 &DesktopNotificationService::OnStringListPrefChanged,
126 base::Unretained(this),
127 base::Unretained(prefs::kMessageCenterDisabledSystemComponentIds),
128 base::Unretained(&disabled_system_component_ids_)));
129 #if defined(ENABLE_EXTENSIONS)
130 extension_registry_observer_.Add(
131 extensions::ExtensionRegistry::Get(profile_));
132 #endif
133 }
134
~DesktopNotificationService()135 DesktopNotificationService::~DesktopNotificationService() {
136 }
137
RequestNotificationPermission(content::WebContents * web_contents,const PermissionRequestID & request_id,const GURL & requesting_frame,bool user_gesture,const NotificationPermissionCallback & callback)138 void DesktopNotificationService::RequestNotificationPermission(
139 content::WebContents* web_contents,
140 const PermissionRequestID& request_id,
141 const GURL& requesting_frame,
142 bool user_gesture,
143 const NotificationPermissionCallback& callback) {
144 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
145 RequestPermission(
146 web_contents,
147 request_id,
148 requesting_frame,
149 user_gesture,
150 base::Bind(&DesktopNotificationService::OnNotificationPermissionRequested,
151 weak_factory_.GetWeakPtr(),
152 callback));
153 }
154
ShowDesktopNotification(const content::ShowDesktopNotificationHostMsgParams & params,content::RenderFrameHost * render_frame_host,scoped_ptr<content::DesktopNotificationDelegate> delegate,base::Closure * cancel_callback)155 void DesktopNotificationService::ShowDesktopNotification(
156 const content::ShowDesktopNotificationHostMsgParams& params,
157 content::RenderFrameHost* render_frame_host,
158 scoped_ptr<content::DesktopNotificationDelegate> delegate,
159 base::Closure* cancel_callback) {
160 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
161 const GURL& origin = params.origin;
162 NotificationObjectProxy* proxy =
163 new NotificationObjectProxy(render_frame_host, delegate.Pass());
164
165 base::string16 display_source = DisplayNameForOriginInProcessId(
166 origin, render_frame_host->GetProcess()->GetID());
167 Notification notification(origin, params.icon_url, params.title,
168 params.body, params.direction, display_source, params.replace_id,
169 proxy);
170
171 // The webkit notification doesn't timeout.
172 notification.set_never_timeout(true);
173
174 g_browser_process->notification_ui_manager()->Add(notification, profile_);
175 if (cancel_callback)
176 *cancel_callback = base::Bind(&CancelNotification, proxy->id());
177
178 DesktopNotificationProfileUtil::UsePermission(profile_, origin);
179 }
180
DisplayNameForOriginInProcessId(const GURL & origin,int process_id)181 base::string16 DesktopNotificationService::DisplayNameForOriginInProcessId(
182 const GURL& origin, int process_id) {
183 #if defined(ENABLE_EXTENSIONS)
184 // If the source is an extension, lookup the display name.
185 if (origin.SchemeIs(extensions::kExtensionScheme)) {
186 extensions::InfoMap* extension_info_map =
187 extensions::ExtensionSystem::Get(profile_)->info_map();
188 if (extension_info_map) {
189 extensions::ExtensionSet extensions;
190 extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
191 origin,
192 process_id,
193 extensions::APIPermission::kNotifications,
194 &extensions);
195 for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
196 iter != extensions.end(); ++iter) {
197 NotifierId notifier_id(NotifierId::APPLICATION, (*iter)->id());
198 if (IsNotifierEnabled(notifier_id))
199 return base::UTF8ToUTF16((*iter)->name());
200 }
201 }
202 }
203 #endif
204
205 return base::UTF8ToUTF16(origin.host());
206 }
207
IsNotifierEnabled(const NotifierId & notifier_id)208 bool DesktopNotificationService::IsNotifierEnabled(
209 const NotifierId& notifier_id) {
210 switch (notifier_id.type) {
211 case NotifierId::APPLICATION:
212 return disabled_extension_ids_.find(notifier_id.id) ==
213 disabled_extension_ids_.end();
214 case NotifierId::WEB_PAGE:
215 return DesktopNotificationProfileUtil::GetContentSetting(
216 profile_, notifier_id.url) == CONTENT_SETTING_ALLOW;
217 case NotifierId::SYSTEM_COMPONENT:
218 #if defined(OS_CHROMEOS)
219 return disabled_system_component_ids_.find(notifier_id.id) ==
220 disabled_system_component_ids_.end();
221 #else
222 // We do not disable system component notifications.
223 return true;
224 #endif
225 }
226
227 NOTREACHED();
228 return false;
229 }
230
SetNotifierEnabled(const NotifierId & notifier_id,bool enabled)231 void DesktopNotificationService::SetNotifierEnabled(
232 const NotifierId& notifier_id,
233 bool enabled) {
234 DCHECK_NE(NotifierId::WEB_PAGE, notifier_id.type);
235
236 bool add_new_item = false;
237 const char* pref_name = NULL;
238 scoped_ptr<base::StringValue> id;
239 switch (notifier_id.type) {
240 case NotifierId::APPLICATION:
241 pref_name = prefs::kMessageCenterDisabledExtensionIds;
242 add_new_item = !enabled;
243 id.reset(new base::StringValue(notifier_id.id));
244 FirePermissionLevelChangedEvent(notifier_id, enabled);
245 break;
246 case NotifierId::SYSTEM_COMPONENT:
247 #if defined(OS_CHROMEOS)
248 pref_name = prefs::kMessageCenterDisabledSystemComponentIds;
249 add_new_item = !enabled;
250 id.reset(new base::StringValue(notifier_id.id));
251 #else
252 return;
253 #endif
254 break;
255 default:
256 NOTREACHED();
257 }
258 DCHECK(pref_name != NULL);
259
260 ListPrefUpdate update(profile_->GetPrefs(), pref_name);
261 base::ListValue* const list = update.Get();
262 if (add_new_item) {
263 // AppendIfNotPresent will delete |adding_value| when the same value
264 // already exists.
265 list->AppendIfNotPresent(id.release());
266 } else {
267 list->Remove(*id, NULL);
268 }
269 }
270
OnStringListPrefChanged(const char * pref_name,std::set<std::string> * ids_field)271 void DesktopNotificationService::OnStringListPrefChanged(
272 const char* pref_name, std::set<std::string>* ids_field) {
273 ids_field->clear();
274 // Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320
275 const PrefService* pref_service = profile_->GetPrefs();
276 CHECK(pref_service);
277 const base::ListValue* pref_list = pref_service->GetList(pref_name);
278 for (size_t i = 0; i < pref_list->GetSize(); ++i) {
279 std::string element;
280 if (pref_list->GetString(i, &element) && !element.empty())
281 ids_field->insert(element);
282 else
283 LOG(WARNING) << i << "-th element is not a string for " << pref_name;
284 }
285 }
286
287 #if defined(ENABLE_EXTENSIONS)
OnExtensionUninstalled(content::BrowserContext * browser_context,const extensions::Extension * extension,extensions::UninstallReason reason)288 void DesktopNotificationService::OnExtensionUninstalled(
289 content::BrowserContext* browser_context,
290 const extensions::Extension* extension,
291 extensions::UninstallReason reason) {
292 NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
293 if (IsNotifierEnabled(notifier_id))
294 return;
295
296 // The settings for ephemeral apps will be persisted across cache evictions.
297 if (extensions::util::IsEphemeralApp(extension->id(), profile_))
298 return;
299
300 SetNotifierEnabled(notifier_id, true);
301 }
302 #endif
303
304 // Unlike other permission types, granting a notification for a given origin
305 // will not take into account the |embedder_origin|, it will only be based
306 // on the requesting iframe origin.
307 // TODO(mukai) Consider why notifications behave differently than
308 // other permissions. crbug.com/416894
UpdateContentSetting(const GURL & requesting_origin,const GURL & embedder_origin,bool allowed)309 void DesktopNotificationService::UpdateContentSetting(
310 const GURL& requesting_origin,
311 const GURL& embedder_origin,
312 bool allowed) {
313 if (allowed) {
314 DesktopNotificationProfileUtil::GrantPermission(
315 profile_, requesting_origin);
316 } else {
317 DesktopNotificationProfileUtil::DenyPermission(profile_, requesting_origin);
318 }
319 }
320
OnNotificationPermissionRequested(const NotificationPermissionCallback & callback,bool allowed)321 void DesktopNotificationService::OnNotificationPermissionRequested(
322 const NotificationPermissionCallback& callback, bool allowed) {
323 blink::WebNotificationPermission permission = allowed ?
324 blink::WebNotificationPermissionAllowed :
325 blink::WebNotificationPermissionDenied;
326
327 callback.Run(permission);
328 }
329
FirePermissionLevelChangedEvent(const NotifierId & notifier_id,bool enabled)330 void DesktopNotificationService::FirePermissionLevelChangedEvent(
331 const NotifierId& notifier_id, bool enabled) {
332 #if defined(ENABLE_EXTENSIONS)
333 DCHECK_EQ(NotifierId::APPLICATION, notifier_id.type);
334 extensions::api::notifications::PermissionLevel permission =
335 enabled ? extensions::api::notifications::PERMISSION_LEVEL_GRANTED
336 : extensions::api::notifications::PERMISSION_LEVEL_DENIED;
337 scoped_ptr<base::ListValue> args(new base::ListValue());
338 args->Append(new base::StringValue(
339 extensions::api::notifications::ToString(permission)));
340 scoped_ptr<extensions::Event> event(new extensions::Event(
341 extensions::api::notifications::OnPermissionLevelChanged::kEventName,
342 args.Pass()));
343 extensions::EventRouter::Get(profile_)
344 ->DispatchEventToExtension(notifier_id.id, event.Pass());
345
346 // Tell the IO thread that this extension's permission for notifications
347 // has changed.
348 extensions::InfoMap* extension_info_map =
349 extensions::ExtensionSystem::Get(profile_)->info_map();
350 BrowserThread::PostTask(
351 BrowserThread::IO, FROM_HERE,
352 base::Bind(&extensions::InfoMap::SetNotificationsDisabled,
353 extension_info_map, notifier_id.id, !enabled));
354 #endif
355 }
356