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 50using blink::WebTextDirection; 51using content::BrowserThread; 52using content::RenderViewHost; 53using content::WebContents; 54using message_center::NotifierId; 55 56namespace { 57 58void CancelNotification(const std::string& id) { 59 g_browser_process->notification_ui_manager()->CancelById(id); 60} 61 62} // namespace 63 64// DesktopNotificationService ------------------------------------------------- 65 66// static 67void 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 78std::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 101DesktopNotificationService::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 135DesktopNotificationService::~DesktopNotificationService() { 136} 137 138void 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 155void 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 181base::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 208bool 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 231void 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 271void 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) 288void 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 309void 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 321void 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 330void 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