• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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