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 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 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 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 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 135 DesktopNotificationService::~DesktopNotificationService() { 136 } 137 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 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 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 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 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 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) 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 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 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 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