desktop_notification_service.cc revision 6e8cce623b6e4fe0c9e4af605d675dd9d0338c38
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/content_settings/content_settings_details.h" 15#include "chrome/browser/content_settings/content_settings_provider.h" 16#include "chrome/browser/notifications/desktop_notification_profile_util.h" 17#include "chrome/browser/notifications/desktop_notification_service_factory.h" 18#include "chrome/browser/notifications/notification.h" 19#include "chrome/browser/notifications/notification_object_proxy.h" 20#include "chrome/browser/notifications/notification_ui_manager.h" 21#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h" 22#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h" 23#include "chrome/browser/profiles/profile.h" 24#include "chrome/browser/ui/browser.h" 25#include "chrome/common/pref_names.h" 26#include "chrome/common/url_constants.h" 27#include "components/pref_registry/pref_registry_syncable.h" 28#include "content/public/browser/browser_thread.h" 29#include "content/public/browser/desktop_notification_delegate.h" 30#include "content/public/browser/notification_service.h" 31#include "content/public/browser/render_frame_host.h" 32#include "content/public/browser/render_process_host.h" 33#include "content/public/browser/render_view_host.h" 34#include "content/public/browser/web_contents.h" 35#include "content/public/common/show_desktop_notification_params.h" 36#include "grit/browser_resources.h" 37#include "grit/chromium_strings.h" 38#include "grit/generated_resources.h" 39#include "grit/theme_resources.h" 40#include "net/base/escape.h" 41#include "ui/base/resource/resource_bundle.h" 42#include "ui/base/webui/web_ui_util.h" 43#include "ui/message_center/notifier_settings.h" 44 45#if defined(ENABLE_EXTENSIONS) 46#include "chrome/browser/extensions/api/notifications/notifications_api.h" 47#include "chrome/browser/extensions/extension_service.h" 48#include "extensions/browser/event_router.h" 49#include "extensions/browser/extension_registry.h" 50#include "extensions/browser/extension_system.h" 51#include "extensions/browser/extension_util.h" 52#include "extensions/browser/info_map.h" 53#include "extensions/common/constants.h" 54#include "extensions/common/extension.h" 55#include "extensions/common/extension_set.h" 56#endif 57 58using blink::WebTextDirection; 59using content::BrowserThread; 60using content::RenderViewHost; 61using content::WebContents; 62using message_center::NotifierId; 63 64namespace { 65 66const char kChromeNowExtensionID[] = "pafkbggdmjlpgkdkcbjmhmfcdpncadgh"; 67 68void CancelNotification(const std::string& id) { 69 g_browser_process->notification_ui_manager()->CancelById(id); 70} 71 72} // namespace 73 74 75// DesktopNotificationService ------------------------------------------------- 76 77// static 78void DesktopNotificationService::RegisterProfilePrefs( 79 user_prefs::PrefRegistrySyncable* registry) { 80 registry->RegisterListPref( 81 prefs::kMessageCenterDisabledExtensionIds, 82 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 83 registry->RegisterListPref( 84 prefs::kMessageCenterDisabledSystemComponentIds, 85 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 86 ExtensionWelcomeNotification::RegisterProfilePrefs(registry); 87} 88 89// static 90base::string16 DesktopNotificationService::CreateDataUrl( 91 const GURL& icon_url, 92 const base::string16& title, 93 const base::string16& body, 94 WebTextDirection dir) { 95 int resource; 96 std::vector<std::string> subst; 97 if (icon_url.is_valid()) { 98 resource = IDR_NOTIFICATION_ICON_HTML; 99 subst.push_back(icon_url.spec()); 100 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title))); 101 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body))); 102 // icon float position 103 subst.push_back(dir == blink::WebTextDirectionRightToLeft ? 104 "right" : "left"); 105 } else if (title.empty() || body.empty()) { 106 resource = IDR_NOTIFICATION_1LINE_HTML; 107 base::string16 line = title.empty() ? body : title; 108 // Strings are div names in the template file. 109 base::string16 line_name = 110 title.empty() ? base::ASCIIToUTF16("description") 111 : base::ASCIIToUTF16("title"); 112 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line_name))); 113 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(line))); 114 } else { 115 resource = IDR_NOTIFICATION_2LINE_HTML; 116 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(title))); 117 subst.push_back(net::EscapeForHTML(base::UTF16ToUTF8(body))); 118 } 119 // body text direction 120 subst.push_back(dir == blink::WebTextDirectionRightToLeft ? 121 "rtl" : "ltr"); 122 123 return CreateDataUrl(resource, subst); 124} 125 126// static 127base::string16 DesktopNotificationService::CreateDataUrl( 128 int resource, const std::vector<std::string>& subst) { 129 const base::StringPiece template_html( 130 ResourceBundle::GetSharedInstance().GetRawDataResource( 131 resource)); 132 133 if (template_html.empty()) { 134 NOTREACHED() << "unable to load template. ID: " << resource; 135 return base::string16(); 136 } 137 138 std::string data = ReplaceStringPlaceholders(template_html, subst, NULL); 139 return base::UTF8ToUTF16("data:text/html;charset=utf-8," + 140 net::EscapeQueryParamValue(data, false)); 141} 142 143// static 144std::string DesktopNotificationService::AddIconNotification( 145 const GURL& origin_url, 146 const base::string16& title, 147 const base::string16& message, 148 const gfx::Image& icon, 149 const base::string16& replace_id, 150 NotificationDelegate* delegate, 151 Profile* profile) { 152 Notification notification(origin_url, icon, title, message, 153 blink::WebTextDirectionDefault, 154 base::string16(), replace_id, delegate); 155 g_browser_process->notification_ui_manager()->Add(notification, profile); 156 return notification.delegate_id(); 157} 158 159DesktopNotificationService::DesktopNotificationService( 160 Profile* profile, 161 NotificationUIManager* ui_manager) 162 : PermissionContextBase(profile, CONTENT_SETTINGS_TYPE_NOTIFICATIONS), 163 profile_(profile), 164 ui_manager_(ui_manager), 165 extension_registry_observer_(this), 166 weak_factory_(this) { 167 OnStringListPrefChanged( 168 prefs::kMessageCenterDisabledExtensionIds, &disabled_extension_ids_); 169 OnStringListPrefChanged( 170 prefs::kMessageCenterDisabledSystemComponentIds, 171 &disabled_system_component_ids_); 172 disabled_extension_id_pref_.Init( 173 prefs::kMessageCenterDisabledExtensionIds, 174 profile_->GetPrefs(), 175 base::Bind( 176 &DesktopNotificationService::OnStringListPrefChanged, 177 base::Unretained(this), 178 base::Unretained(prefs::kMessageCenterDisabledExtensionIds), 179 base::Unretained(&disabled_extension_ids_))); 180 disabled_system_component_id_pref_.Init( 181 prefs::kMessageCenterDisabledSystemComponentIds, 182 profile_->GetPrefs(), 183 base::Bind( 184 &DesktopNotificationService::OnStringListPrefChanged, 185 base::Unretained(this), 186 base::Unretained(prefs::kMessageCenterDisabledSystemComponentIds), 187 base::Unretained(&disabled_system_component_ids_))); 188 extension_registry_observer_.Add( 189 extensions::ExtensionRegistry::Get(profile_)); 190} 191 192DesktopNotificationService::~DesktopNotificationService() { 193} 194 195void DesktopNotificationService::RequestNotificationPermission( 196 content::WebContents* web_contents, 197 const PermissionRequestID& request_id, 198 const GURL& requesting_frame, 199 bool user_gesture, 200 const NotificationPermissionCallback& callback) { 201 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 202 RequestPermission( 203 web_contents, 204 request_id, 205 requesting_frame, 206 user_gesture, 207 base::Bind(&DesktopNotificationService::OnNotificationPermissionRequested, 208 weak_factory_.GetWeakPtr(), 209 callback)); 210} 211 212void DesktopNotificationService::ShowDesktopNotification( 213 const content::ShowDesktopNotificationHostMsgParams& params, 214 content::RenderFrameHost* render_frame_host, 215 scoped_ptr<content::DesktopNotificationDelegate> delegate, 216 base::Closure* cancel_callback) { 217 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 218 const GURL& origin = params.origin; 219 NotificationObjectProxy* proxy = 220 new NotificationObjectProxy(render_frame_host, delegate.Pass()); 221 222 base::string16 display_source = DisplayNameForOriginInProcessId( 223 origin, render_frame_host->GetProcess()->GetID()); 224 Notification notification(origin, params.icon_url, params.title, 225 params.body, params.direction, display_source, params.replace_id, 226 proxy); 227 228 // The webkit notification doesn't timeout. 229 notification.set_never_timeout(true); 230 231 GetUIManager()->Add(notification, profile_); 232 if (cancel_callback) 233 *cancel_callback = base::Bind(&CancelNotification, proxy->id()); 234 235 DesktopNotificationProfileUtil::UsePermission(profile_, origin); 236} 237 238base::string16 DesktopNotificationService::DisplayNameForOriginInProcessId( 239 const GURL& origin, int process_id) { 240#if defined(ENABLE_EXTENSIONS) 241 // If the source is an extension, lookup the display name. 242 if (origin.SchemeIs(extensions::kExtensionScheme)) { 243 extensions::InfoMap* extension_info_map = 244 extensions::ExtensionSystem::Get(profile_)->info_map(); 245 if (extension_info_map) { 246 extensions::ExtensionSet extensions; 247 extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin( 248 origin, 249 process_id, 250 extensions::APIPermission::kNotifications, 251 &extensions); 252 for (extensions::ExtensionSet::const_iterator iter = extensions.begin(); 253 iter != extensions.end(); ++iter) { 254 NotifierId notifier_id(NotifierId::APPLICATION, (*iter)->id()); 255 if (IsNotifierEnabled(notifier_id)) 256 return base::UTF8ToUTF16((*iter)->name()); 257 } 258 } 259 } 260#endif 261 262 return base::UTF8ToUTF16(origin.host()); 263} 264 265NotificationUIManager* DesktopNotificationService::GetUIManager() { 266 // We defer setting ui_manager_ to the global singleton until we need it 267 // in order to avoid UI dependent construction during startup. 268 if (!ui_manager_) 269 ui_manager_ = g_browser_process->notification_ui_manager(); 270 return ui_manager_; 271} 272 273bool DesktopNotificationService::IsNotifierEnabled( 274 const NotifierId& notifier_id) { 275 switch (notifier_id.type) { 276 case NotifierId::APPLICATION: 277 return disabled_extension_ids_.find(notifier_id.id) == 278 disabled_extension_ids_.end(); 279 case NotifierId::WEB_PAGE: 280 return DesktopNotificationProfileUtil::GetContentSetting( 281 profile_, notifier_id.url) == CONTENT_SETTING_ALLOW; 282 case NotifierId::SYSTEM_COMPONENT: 283#if defined(OS_CHROMEOS) 284 return disabled_system_component_ids_.find(notifier_id.id) == 285 disabled_system_component_ids_.end(); 286#else 287 // We do not disable system component notifications. 288 return true; 289#endif 290 } 291 292 NOTREACHED(); 293 return false; 294} 295 296void DesktopNotificationService::SetNotifierEnabled( 297 const NotifierId& notifier_id, 298 bool enabled) { 299 DCHECK_NE(NotifierId::WEB_PAGE, notifier_id.type); 300 301 bool add_new_item = false; 302 const char* pref_name = NULL; 303 scoped_ptr<base::StringValue> id; 304 switch (notifier_id.type) { 305 case NotifierId::APPLICATION: 306 pref_name = prefs::kMessageCenterDisabledExtensionIds; 307 add_new_item = !enabled; 308 id.reset(new base::StringValue(notifier_id.id)); 309 FirePermissionLevelChangedEvent(notifier_id, enabled); 310 break; 311 case NotifierId::SYSTEM_COMPONENT: 312#if defined(OS_CHROMEOS) 313 pref_name = prefs::kMessageCenterDisabledSystemComponentIds; 314 add_new_item = !enabled; 315 id.reset(new base::StringValue(notifier_id.id)); 316#else 317 return; 318#endif 319 break; 320 default: 321 NOTREACHED(); 322 } 323 DCHECK(pref_name != NULL); 324 325 ListPrefUpdate update(profile_->GetPrefs(), pref_name); 326 base::ListValue* const list = update.Get(); 327 if (add_new_item) { 328 // AppendIfNotPresent will delete |adding_value| when the same value 329 // already exists. 330 list->AppendIfNotPresent(id.release()); 331 } else { 332 list->Remove(*id, NULL); 333 } 334} 335 336void DesktopNotificationService::ShowWelcomeNotificationIfNecessary( 337 const Notification& notification) { 338 if (!chrome_now_welcome_notification_) { 339 chrome_now_welcome_notification_ = 340 ExtensionWelcomeNotification::Create(kChromeNowExtensionID, profile_); 341 } 342 343 if (chrome_now_welcome_notification_) { 344 chrome_now_welcome_notification_->ShowWelcomeNotificationIfNecessary( 345 notification); 346 } 347} 348 349void DesktopNotificationService::OnStringListPrefChanged( 350 const char* pref_name, std::set<std::string>* ids_field) { 351 ids_field->clear(); 352 // Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320 353 const PrefService* pref_service = profile_->GetPrefs(); 354 CHECK(pref_service); 355 const base::ListValue* pref_list = pref_service->GetList(pref_name); 356 for (size_t i = 0; i < pref_list->GetSize(); ++i) { 357 std::string element; 358 if (pref_list->GetString(i, &element) && !element.empty()) 359 ids_field->insert(element); 360 else 361 LOG(WARNING) << i << "-th element is not a string for " << pref_name; 362 } 363} 364 365void DesktopNotificationService::OnExtensionUninstalled( 366 content::BrowserContext* browser_context, 367 const extensions::Extension* extension, 368 extensions::UninstallReason reason) { 369#if defined(ENABLE_EXTENSIONS) 370 NotifierId notifier_id(NotifierId::APPLICATION, extension->id()); 371 if (IsNotifierEnabled(notifier_id)) 372 return; 373 374 // The settings for ephemeral apps will be persisted across cache evictions. 375 if (extensions::util::IsEphemeralApp(extension->id(), profile_)) 376 return; 377 378 SetNotifierEnabled(notifier_id, true); 379#endif 380} 381 382void DesktopNotificationService::OnNotificationPermissionRequested( 383 const NotificationPermissionCallback& callback, bool allowed) { 384 blink::WebNotificationPermission permission = allowed ? 385 blink::WebNotificationPermissionAllowed : 386 blink::WebNotificationPermissionDenied; 387 388 callback.Run(permission); 389} 390 391void DesktopNotificationService::FirePermissionLevelChangedEvent( 392 const NotifierId& notifier_id, bool enabled) { 393#if defined(ENABLE_EXTENSIONS) 394 DCHECK_EQ(NotifierId::APPLICATION, notifier_id.type); 395 extensions::api::notifications::PermissionLevel permission = 396 enabled ? extensions::api::notifications::PERMISSION_LEVEL_GRANTED 397 : extensions::api::notifications::PERMISSION_LEVEL_DENIED; 398 scoped_ptr<base::ListValue> args(new base::ListValue()); 399 args->Append(new base::StringValue( 400 extensions::api::notifications::ToString(permission))); 401 scoped_ptr<extensions::Event> event(new extensions::Event( 402 extensions::api::notifications::OnPermissionLevelChanged::kEventName, 403 args.Pass())); 404 extensions::EventRouter::Get(profile_) 405 ->DispatchEventToExtension(notifier_id.id, event.Pass()); 406 407 // Tell the IO thread that this extension's permission for notifications 408 // has changed. 409 extensions::InfoMap* extension_info_map = 410 extensions::ExtensionSystem::Get(profile_)->info_map(); 411 BrowserThread::PostTask( 412 BrowserThread::IO, FROM_HERE, 413 base::Bind(&extensions::InfoMap::SetNotificationsDisabled, 414 extension_info_map, notifier_id.id, !enabled)); 415#endif 416} 417