Adding instrumentation to locate the source of jankiness
[chromium-blink-merge.git] / chrome / browser / notifications / desktop_notification_service.cc
blobfad44eb4d08637db2d682900097157e443a90a9d
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"
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/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"
50 #endif
52 using blink::WebTextDirection;
53 using content::BrowserThread;
54 using content::RenderViewHost;
55 using content::WebContents;
56 using message_center::NotifierId;
58 namespace {
60 void CancelNotification(const std::string& id, ProfileID profile_id) {
61 g_browser_process->notification_ui_manager()->CancelById(id, profile_id);
64 } // namespace
66 // DesktopNotificationService -------------------------------------------------
68 // static
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);
79 // static
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,
87 Profile* profile) {
88 Notification notification(message_center::NOTIFICATION_TYPE_SIMPLE,
89 origin_url,
90 title,
91 message,
92 icon,
93 blink::WebTextDirectionDefault,
94 message_center::NotifierId(origin_url),
95 base::string16(),
96 replace_id,
97 message_center::RichNotificationData(),
98 delegate);
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),
105 profile_(profile),
106 #if defined(ENABLE_EXTENSIONS)
107 extension_registry_observer_(this),
108 #endif
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(),
118 base::Bind(
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(),
126 base::Bind(
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_));
134 #endif
137 DesktopNotificationService::~DesktopNotificationService() {
140 void DesktopNotificationService::RequestNotificationPermission(
141 content::WebContents* web_contents,
142 const PermissionRequestID& request_id,
143 const GURL& requesting_origin,
144 bool user_gesture,
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(
155 requesting_origin,
156 request_id.render_process_id(),
157 extensions::APIPermission::kNotifications,
158 &extensions);
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();
164 break;
168 if (IsExtensionWithPermissionOrSuggestInConsole(
169 extensions::APIPermission::kNotifications,
170 extension,
171 web_contents->GetRenderViewHost())) {
172 callback.Run(blink::WebNotificationPermissionAllowed);
173 return;
175 #endif
177 RequestPermission(
178 web_contents,
179 request_id,
180 requesting_origin,
181 user_gesture,
182 base::Bind(&DesktopNotificationService::OnNotificationPermissionRequested,
183 weak_factory_.GetWeakPtr(),
184 callback));
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_);
211 if (cancel_callback)
212 *cancel_callback =
213 base::Bind(&CancelNotification,
214 proxy->id(),
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(
230 origin,
231 process_id,
232 extensions::APIPermission::kNotifications,
233 &extensions);
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());
242 #endif
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();
260 #else
261 // We do not disable system component notifications.
262 return true;
263 #endif
266 NOTREACHED();
267 return false;
270 void DesktopNotificationService::SetNotifierEnabled(
271 const NotifierId& notifier_id,
272 bool enabled) {
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);
284 break;
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));
290 #else
291 return;
292 #endif
293 break;
294 default:
295 NOTREACHED();
297 DCHECK(pref_name != NULL);
299 ListPrefUpdate update(profile_->GetPrefs(), pref_name);
300 base::ListValue* const list = update.Get();
301 if (add_new_item) {
302 // AppendIfNotPresent will delete |adding_value| when the same value
303 // already exists.
304 list->AppendIfNotPresent(id.release());
305 } else {
306 list->Remove(*id, NULL);
310 void DesktopNotificationService::OnStringListPrefChanged(
311 const char* pref_name, std::set<std::string>* ids_field) {
312 ids_field->clear();
313 // Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320
314 const PrefService* pref_service = profile_->GetPrefs();
315 CHECK(pref_service);
316 const base::ListValue* pref_list = pref_service->GetList(pref_name);
317 for (size_t i = 0; i < pref_list->GetSize(); ++i) {
318 std::string element;
319 if (pref_list->GetString(i, &element) && !element.empty())
320 ids_field->insert(element);
321 else
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))
333 return;
335 // The settings for ephemeral apps will be persisted across cache evictions.
336 if (extensions::util::IsEphemeralApp(extension->id(), profile_))
337 return;
339 SetNotifierEnabled(notifier_id, true);
341 #endif
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,
351 bool allowed) {
352 if (allowed) {
353 DesktopNotificationProfileUtil::GrantPermission(
354 profile_, requesting_origin);
355 } else {
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,
381 args.Pass()));
382 extensions::EventRouter::Get(profile_)
383 ->DispatchEventToExtension(notifier_id.id, event.Pass());
385 // Tell the IO thread that this extension's permission for notifications
386 // has changed.
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));
393 #endif