1 // Copyright (c) 2011 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/metrics/histogram.h" 8 #include "base/threading/thread.h" 9 #include "base/utf_string_conversions.h" 10 #include "chrome/browser/content_settings/content_settings_provider.h" 11 #include "chrome/browser/extensions/extension_service.h" 12 #include "chrome/browser/notifications/desktop_notification_service_factory.h" 13 #include "chrome/browser/notifications/notification.h" 14 #include "chrome/browser/notifications/notification_object_proxy.h" 15 #include "chrome/browser/notifications/notification_ui_manager.h" 16 #include "chrome/browser/notifications/notifications_prefs_cache.h" 17 #include "chrome/browser/prefs/pref_service.h" 18 #include "chrome/browser/prefs/scoped_user_pref_update.h" 19 #include "chrome/browser/profiles/profile.h" 20 #include "chrome/browser/tab_contents/confirm_infobar_delegate.h" 21 #include "chrome/browser/ui/browser_list.h" 22 #include "chrome/common/pref_names.h" 23 #include "chrome/common/url_constants.h" 24 #include "content/browser/browser_child_process_host.h" 25 #include "content/browser/browser_thread.h" 26 #include "content/browser/renderer_host/render_process_host.h" 27 #include "content/browser/renderer_host/render_view_host.h" 28 #include "content/browser/site_instance.h" 29 #include "content/browser/tab_contents/tab_contents.h" 30 #include "content/browser/worker_host/worker_process_host.h" 31 #include "content/common/desktop_notification_messages.h" 32 #include "content/common/notification_service.h" 33 #include "content/common/notification_type.h" 34 #include "grit/browser_resources.h" 35 #include "grit/chromium_strings.h" 36 #include "grit/generated_resources.h" 37 #include "grit/theme_resources.h" 38 #include "net/base/escape.h" 39 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNotificationPresenter.h" 40 #include "ui/base/l10n/l10n_util.h" 41 #include "ui/base/resource/resource_bundle.h" 42 43 using WebKit::WebNotificationPresenter; 44 using WebKit::WebTextDirection; 45 46 const ContentSetting kDefaultSetting = CONTENT_SETTING_ASK; 47 48 namespace { 49 50 typedef content_settings::ProviderInterface::Rules Rules; 51 52 void GetOriginsWithSettingFromContentSettingsRules( 53 const Rules& content_setting_rules, 54 ContentSetting setting, 55 std::vector<GURL>* origins) { 56 origins->clear(); 57 58 for (Rules::const_iterator rule = content_setting_rules.begin(); 59 rule != content_setting_rules.end(); 60 ++rule) { 61 if (setting == rule->content_setting) { 62 std::string url_str = rule->requesting_url_pattern.AsString(); 63 if (!rule->requesting_url_pattern.IsValid()) { 64 // TODO(markusheintz): This will be removed in one of the next 65 // refactoring steps as this entire function will disapear. 66 LOG(DFATAL) << "Ignoring invalid content settings pattern: " 67 << url_str; 68 } else if (url_str.find(ContentSettingsPattern::kDomainWildcard) == 0) { 69 // TODO(markusheintz): This must be changed once the UI code is 70 // refactored and content_settings patterns are fully supported for 71 // notifications settings. 72 LOG(DFATAL) << "Ignoring unsupported content settings pattern: " 73 << url_str << ". Content settings patterns other than " 74 << "hostnames (e.g. wildcard patterns) are not supported " 75 << "for notification content settings yet."; 76 } else { 77 origins->push_back( 78 content_settings::NotificationProvider::ToGURL( 79 rule->requesting_url_pattern)); 80 } 81 } 82 } 83 } 84 85 } // namespace 86 87 // NotificationPermissionInfoBarDelegate -------------------------------------- 88 89 // The delegate for the infobar shown when an origin requests notification 90 // permissions. 91 class NotificationPermissionInfoBarDelegate : public ConfirmInfoBarDelegate { 92 public: 93 NotificationPermissionInfoBarDelegate(TabContents* contents, 94 const GURL& origin, 95 const string16& display_name, 96 int process_id, 97 int route_id, 98 int callback_context); 99 100 private: 101 virtual ~NotificationPermissionInfoBarDelegate(); 102 103 // ConfirmInfoBarDelegate: 104 virtual void InfoBarClosed(); 105 virtual SkBitmap* GetIcon() const; 106 virtual Type GetInfoBarType() const; 107 virtual string16 GetMessageText() const; 108 virtual string16 GetButtonLabel(InfoBarButton button) const; 109 virtual bool Accept(); 110 virtual bool Cancel(); 111 112 // The origin we are asking for permissions on. 113 GURL origin_; 114 115 // The display name for the origin to be displayed. Will be different from 116 // origin_ for extensions. 117 string16 display_name_; 118 119 // The Profile that we restore sessions from. 120 Profile* profile_; 121 122 // The callback information that tells us how to respond to javascript via 123 // the correct RenderView. 124 int process_id_; 125 int route_id_; 126 int callback_context_; 127 128 // Whether the user clicked one of the buttons. 129 bool action_taken_; 130 131 DISALLOW_COPY_AND_ASSIGN(NotificationPermissionInfoBarDelegate); 132 }; 133 134 NotificationPermissionInfoBarDelegate::NotificationPermissionInfoBarDelegate( 135 TabContents* contents, 136 const GURL& origin, 137 const string16& display_name, 138 int process_id, 139 int route_id, 140 int callback_context) 141 : ConfirmInfoBarDelegate(contents), 142 origin_(origin), 143 display_name_(display_name), 144 profile_(contents->profile()), 145 process_id_(process_id), 146 route_id_(route_id), 147 callback_context_(callback_context), 148 action_taken_(false) { 149 } 150 151 NotificationPermissionInfoBarDelegate:: 152 ~NotificationPermissionInfoBarDelegate() { 153 } 154 155 void NotificationPermissionInfoBarDelegate::InfoBarClosed() { 156 if (!action_taken_) 157 UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Ignored", 1); 158 159 RenderViewHost* host = RenderViewHost::FromID(process_id_, route_id_); 160 if (host) { 161 host->Send(new DesktopNotificationMsg_PermissionRequestDone( 162 route_id_, callback_context_)); 163 } 164 165 delete this; 166 } 167 168 SkBitmap* NotificationPermissionInfoBarDelegate::GetIcon() const { 169 return ResourceBundle::GetSharedInstance().GetBitmapNamed( 170 IDR_PRODUCT_ICON_32); 171 } 172 173 InfoBarDelegate::Type 174 NotificationPermissionInfoBarDelegate::GetInfoBarType() const { 175 return PAGE_ACTION_TYPE; 176 } 177 178 string16 NotificationPermissionInfoBarDelegate::GetMessageText() const { 179 return l10n_util::GetStringFUTF16(IDS_NOTIFICATION_PERMISSIONS, 180 display_name_); 181 } 182 183 string16 NotificationPermissionInfoBarDelegate::GetButtonLabel( 184 InfoBarButton button) const { 185 return l10n_util::GetStringUTF16((button == BUTTON_OK) ? 186 IDS_NOTIFICATION_PERMISSION_YES : IDS_NOTIFICATION_PERMISSION_NO); 187 } 188 189 bool NotificationPermissionInfoBarDelegate::Accept() { 190 UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Allowed", 1); 191 DesktopNotificationServiceFactory::GetForProfile(profile_)-> 192 GrantPermission(origin_); 193 action_taken_ = true; 194 return true; 195 } 196 197 bool NotificationPermissionInfoBarDelegate::Cancel() { 198 UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Denied", 1); 199 DesktopNotificationServiceFactory::GetForProfile(profile_)-> 200 DenyPermission(origin_); 201 action_taken_ = true; 202 return true; 203 } 204 205 206 // DesktopNotificationService ------------------------------------------------- 207 208 // static 209 string16 DesktopNotificationService::CreateDataUrl( 210 const GURL& icon_url, const string16& title, const string16& body, 211 WebTextDirection dir) { 212 int resource; 213 std::vector<std::string> subst; 214 if (icon_url.is_valid()) { 215 resource = IDR_NOTIFICATION_ICON_HTML; 216 subst.push_back(icon_url.spec()); 217 subst.push_back(EscapeForHTML(UTF16ToUTF8(title))); 218 subst.push_back(EscapeForHTML(UTF16ToUTF8(body))); 219 // icon float position 220 subst.push_back(dir == WebKit::WebTextDirectionRightToLeft ? 221 "right" : "left"); 222 } else if (title.empty() || body.empty()) { 223 resource = IDR_NOTIFICATION_1LINE_HTML; 224 string16 line = title.empty() ? body : title; 225 // Strings are div names in the template file. 226 string16 line_name = title.empty() ? ASCIIToUTF16("description") 227 : ASCIIToUTF16("title"); 228 subst.push_back(EscapeForHTML(UTF16ToUTF8(line_name))); 229 subst.push_back(EscapeForHTML(UTF16ToUTF8(line))); 230 } else { 231 resource = IDR_NOTIFICATION_2LINE_HTML; 232 subst.push_back(EscapeForHTML(UTF16ToUTF8(title))); 233 subst.push_back(EscapeForHTML(UTF16ToUTF8(body))); 234 } 235 // body text direction 236 subst.push_back(dir == WebKit::WebTextDirectionRightToLeft ? 237 "rtl" : "ltr"); 238 239 return CreateDataUrl(resource, subst); 240 } 241 242 // static 243 string16 DesktopNotificationService::CreateDataUrl( 244 int resource, const std::vector<std::string>& subst) { 245 const base::StringPiece template_html( 246 ResourceBundle::GetSharedInstance().GetRawDataResource( 247 resource)); 248 249 if (template_html.empty()) { 250 NOTREACHED() << "unable to load template. ID: " << resource; 251 return string16(); 252 } 253 254 std::string data = ReplaceStringPlaceholders(template_html, subst, NULL); 255 return UTF8ToUTF16("data:text/html;charset=utf-8," + 256 EscapeQueryParamValue(data, false)); 257 } 258 259 DesktopNotificationService::DesktopNotificationService(Profile* profile, 260 NotificationUIManager* ui_manager) 261 : profile_(profile), 262 ui_manager_(ui_manager) { 263 prefs_registrar_.Init(profile_->GetPrefs()); 264 InitPrefs(); 265 StartObserving(); 266 } 267 268 DesktopNotificationService::~DesktopNotificationService() { 269 StopObserving(); 270 } 271 272 void DesktopNotificationService::RegisterUserPrefs(PrefService* user_prefs) { 273 content_settings::NotificationProvider::RegisterUserPrefs(user_prefs); 274 } 275 276 // Initialize the cache with the allowed and denied origins, or 277 // create the preferences if they don't exist yet. 278 void DesktopNotificationService::InitPrefs() { 279 provider_.reset(new content_settings::NotificationProvider(profile_)); 280 281 std::vector<GURL> allowed_origins; 282 std::vector<GURL> denied_origins; 283 ContentSetting default_content_setting = CONTENT_SETTING_DEFAULT; 284 285 if (!profile_->IsOffTheRecord()) { 286 default_content_setting = 287 profile_->GetHostContentSettingsMap()->GetDefaultContentSetting( 288 CONTENT_SETTINGS_TYPE_NOTIFICATIONS); 289 allowed_origins = GetAllowedOrigins(); 290 denied_origins = GetBlockedOrigins(); 291 } 292 293 prefs_cache_ = new NotificationsPrefsCache(); 294 prefs_cache_->SetCacheDefaultContentSetting(default_content_setting); 295 prefs_cache_->SetCacheAllowedOrigins(allowed_origins); 296 prefs_cache_->SetCacheDeniedOrigins(denied_origins); 297 prefs_cache_->set_is_initialized(true); 298 } 299 300 void DesktopNotificationService::StartObserving() { 301 if (!profile_->IsOffTheRecord()) { 302 prefs_registrar_.Add(prefs::kDesktopNotificationAllowedOrigins, this); 303 prefs_registrar_.Add(prefs::kDesktopNotificationDeniedOrigins, this); 304 notification_registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, 305 NotificationService::AllSources()); 306 notification_registrar_.Add( 307 this, 308 NotificationType::CONTENT_SETTINGS_CHANGED, 309 // TODO(markusheintz): Remember to change to HostContentSettingsMap. 310 NotificationService::AllSources()); 311 } 312 notification_registrar_.Add(this, NotificationType::PROFILE_DESTROYED, 313 Source<Profile>(profile_)); 314 } 315 316 void DesktopNotificationService::StopObserving() { 317 if (!profile_->IsOffTheRecord()) { 318 prefs_registrar_.RemoveAll(); 319 } 320 notification_registrar_.RemoveAll(); 321 } 322 323 void DesktopNotificationService::GrantPermission(const GURL& origin) { 324 ContentSettingsPattern pattern = 325 content_settings::NotificationProvider::ToContentSettingsPattern(origin); 326 provider_->SetContentSetting( 327 pattern, 328 pattern, 329 CONTENT_SETTINGS_TYPE_NOTIFICATIONS, 330 NO_RESOURCE_IDENTIFIER, 331 CONTENT_SETTING_ALLOW); 332 333 // Schedule a cache update on the IO thread. 334 BrowserThread::PostTask( 335 BrowserThread::IO, FROM_HERE, 336 NewRunnableMethod( 337 prefs_cache_.get(), 338 &NotificationsPrefsCache::CacheAllowedOrigin, 339 origin)); 340 } 341 342 void DesktopNotificationService::DenyPermission(const GURL& origin) { 343 // Update content settings 344 ContentSettingsPattern pattern = 345 content_settings::NotificationProvider::ToContentSettingsPattern(origin); 346 provider_->SetContentSetting( 347 pattern, 348 pattern, 349 CONTENT_SETTINGS_TYPE_NOTIFICATIONS, 350 NO_RESOURCE_IDENTIFIER, 351 CONTENT_SETTING_BLOCK); 352 353 // Schedule a cache update on the IO thread. 354 BrowserThread::PostTask( 355 BrowserThread::IO, FROM_HERE, 356 NewRunnableMethod( 357 prefs_cache_.get(), 358 &NotificationsPrefsCache::CacheDeniedOrigin, 359 origin)); 360 } 361 362 void DesktopNotificationService::Observe(NotificationType type, 363 const NotificationSource& source, 364 const NotificationDetails& details) { 365 if (NotificationType::PREF_CHANGED == type) { 366 const std::string& name = *Details<std::string>(details).ptr(); 367 OnPrefsChanged(name); 368 } else if (NotificationType::CONTENT_SETTINGS_CHANGED == type) { 369 // TODO(markusheintz): Check if content settings type default was changed; 370 const ContentSetting default_content_setting = 371 profile_->GetHostContentSettingsMap()->GetDefaultContentSetting( 372 CONTENT_SETTINGS_TYPE_NOTIFICATIONS); 373 // Schedule a cache update on the IO thread. 374 BrowserThread::PostTask( 375 BrowserThread::IO, FROM_HERE, 376 NewRunnableMethod( 377 prefs_cache_.get(), 378 &NotificationsPrefsCache::SetCacheDefaultContentSetting, 379 default_content_setting)); 380 } else if (NotificationType::EXTENSION_UNLOADED == type) { 381 // Remove all notifications currently shown or queued by the extension 382 // which was unloaded. 383 const Extension* extension = 384 Details<UnloadedExtensionInfo>(details)->extension; 385 if (extension) 386 ui_manager_->CancelAllBySourceOrigin(extension->url()); 387 } else if (NotificationType::PROFILE_DESTROYED == type) { 388 StopObserving(); 389 } 390 } 391 392 void DesktopNotificationService::OnPrefsChanged(const std::string& pref_name) { 393 if (pref_name == prefs::kDesktopNotificationAllowedOrigins) { 394 // Schedule a cache update on the IO thread. 395 std::vector<GURL> allowed_origins(GetAllowedOrigins()); 396 BrowserThread::PostTask( 397 BrowserThread::IO, FROM_HERE, 398 NewRunnableMethod( 399 prefs_cache_.get(), 400 &NotificationsPrefsCache::SetCacheAllowedOrigins, 401 allowed_origins)); 402 } else if (pref_name == prefs::kDesktopNotificationDeniedOrigins) { 403 // Schedule a cache update on the IO thread. 404 std::vector<GURL> denied_origins(GetBlockedOrigins()); 405 BrowserThread::PostTask( 406 BrowserThread::IO, FROM_HERE, 407 NewRunnableMethod( 408 prefs_cache_.get(), 409 &NotificationsPrefsCache::SetCacheDeniedOrigins, 410 denied_origins)); 411 } else { 412 NOTREACHED(); 413 } 414 } 415 416 ContentSetting DesktopNotificationService::GetDefaultContentSetting() { 417 return profile_->GetHostContentSettingsMap()->GetDefaultContentSetting( 418 CONTENT_SETTINGS_TYPE_NOTIFICATIONS); 419 } 420 421 void DesktopNotificationService::SetDefaultContentSetting( 422 ContentSetting setting) { 423 profile_->GetHostContentSettingsMap()->SetDefaultContentSetting( 424 CONTENT_SETTINGS_TYPE_NOTIFICATIONS, setting); 425 } 426 427 bool DesktopNotificationService::IsDefaultContentSettingManaged() const { 428 return profile_->GetHostContentSettingsMap()->IsDefaultContentSettingManaged( 429 CONTENT_SETTINGS_TYPE_NOTIFICATIONS); 430 } 431 432 void DesktopNotificationService::ResetToDefaultContentSetting() { 433 profile_->GetHostContentSettingsMap()->SetDefaultContentSetting( 434 CONTENT_SETTINGS_TYPE_NOTIFICATIONS, CONTENT_SETTING_DEFAULT); 435 } 436 437 std::vector<GURL> DesktopNotificationService::GetAllowedOrigins() { 438 content_settings::ProviderInterface::Rules content_setting_rules; 439 provider_->GetAllContentSettingsRules( 440 CONTENT_SETTINGS_TYPE_NOTIFICATIONS, 441 NO_RESOURCE_IDENTIFIER, 442 &content_setting_rules); 443 std::vector<GURL> allowed_origins; 444 445 GetOriginsWithSettingFromContentSettingsRules( 446 content_setting_rules, CONTENT_SETTING_ALLOW, &allowed_origins); 447 448 return allowed_origins; 449 } 450 451 std::vector<GURL> DesktopNotificationService::GetBlockedOrigins() { 452 content_settings::ProviderInterface::Rules content_settings_rules; 453 provider_->GetAllContentSettingsRules( 454 CONTENT_SETTINGS_TYPE_NOTIFICATIONS, 455 NO_RESOURCE_IDENTIFIER, 456 &content_settings_rules); 457 std::vector<GURL> denied_origins; 458 459 GetOriginsWithSettingFromContentSettingsRules( 460 content_settings_rules, CONTENT_SETTING_BLOCK, &denied_origins); 461 462 return denied_origins; 463 } 464 465 void DesktopNotificationService::ResetAllowedOrigin(const GURL& origin) { 466 ContentSettingsPattern pattern = 467 ContentSettingsPattern::FromURLNoWildcard(origin); 468 provider_->SetContentSetting( 469 pattern, 470 pattern, 471 CONTENT_SETTINGS_TYPE_NOTIFICATIONS, 472 NO_RESOURCE_IDENTIFIER, 473 CONTENT_SETTING_DEFAULT); 474 } 475 476 void DesktopNotificationService::ResetBlockedOrigin(const GURL& origin) { 477 ContentSettingsPattern pattern = 478 ContentSettingsPattern::FromURLNoWildcard(origin); 479 provider_->SetContentSetting( 480 pattern, 481 pattern, 482 CONTENT_SETTINGS_TYPE_NOTIFICATIONS, 483 NO_RESOURCE_IDENTIFIER, 484 CONTENT_SETTING_DEFAULT); 485 } 486 487 void DesktopNotificationService::ResetAllOrigins() { 488 provider_->ClearAllContentSettingsRules(CONTENT_SETTINGS_TYPE_NOTIFICATIONS); 489 } 490 491 ContentSetting DesktopNotificationService::GetContentSetting( 492 const GURL& origin) { 493 ContentSetting provided_setting = provider_->GetContentSetting( 494 origin, 495 origin, 496 CONTENT_SETTINGS_TYPE_NOTIFICATIONS, 497 NO_RESOURCE_IDENTIFIER); 498 if (CONTENT_SETTING_DEFAULT == provided_setting) 499 return GetDefaultContentSetting(); 500 return provided_setting; 501 } 502 503 void DesktopNotificationService::RequestPermission( 504 const GURL& origin, int process_id, int route_id, int callback_context, 505 TabContents* tab) { 506 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 507 if (!tab) { 508 Browser* browser = BrowserList::GetLastActive(); 509 if (browser) 510 tab = browser->GetSelectedTabContents(); 511 } 512 513 if (!tab) 514 return; 515 516 // If |origin| hasn't been seen before and the default content setting for 517 // notifications is "ask", show an infobar. 518 // The cache can only answer queries on the IO thread once it's initialized, 519 // so don't ask the cache. 520 ContentSetting setting = GetContentSetting(origin); 521 if (setting == CONTENT_SETTING_ASK) { 522 // Show an info bar requesting permission. 523 tab->AddInfoBar(new NotificationPermissionInfoBarDelegate( 524 tab, origin, DisplayNameForOrigin(origin), process_id, 525 route_id, callback_context)); 526 } else { 527 // Notify renderer immediately. 528 RenderViewHost* host = RenderViewHost::FromID(process_id, route_id); 529 if (host) { 530 host->Send(new DesktopNotificationMsg_PermissionRequestDone( 531 route_id, callback_context)); 532 } 533 } 534 } 535 536 void DesktopNotificationService::ShowNotification( 537 const Notification& notification) { 538 ui_manager_->Add(notification, profile_); 539 } 540 541 bool DesktopNotificationService::CancelDesktopNotification( 542 int process_id, int route_id, int notification_id) { 543 scoped_refptr<NotificationObjectProxy> proxy( 544 new NotificationObjectProxy(process_id, route_id, notification_id, 545 false)); 546 return ui_manager_->CancelById(proxy->id()); 547 } 548 549 550 bool DesktopNotificationService::ShowDesktopNotification( 551 const DesktopNotificationHostMsg_Show_Params& params, 552 int process_id, int route_id, DesktopNotificationSource source) { 553 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 554 const GURL& origin = params.origin; 555 NotificationObjectProxy* proxy = 556 new NotificationObjectProxy(process_id, route_id, 557 params.notification_id, 558 source == WorkerNotification); 559 GURL contents; 560 if (params.is_html) { 561 contents = params.contents_url; 562 } else { 563 // "upconvert" the string parameters to a data: URL. 564 contents = GURL( 565 CreateDataUrl(params.icon_url, params.title, params.body, 566 params.direction)); 567 } 568 Notification notification( 569 origin, contents, DisplayNameForOrigin(origin), 570 params.replace_id, proxy); 571 ShowNotification(notification); 572 return true; 573 } 574 575 string16 DesktopNotificationService::DisplayNameForOrigin( 576 const GURL& origin) { 577 // If the source is an extension, lookup the display name. 578 if (origin.SchemeIs(chrome::kExtensionScheme)) { 579 ExtensionService* ext_service = profile_->GetExtensionService(); 580 if (ext_service) { 581 const Extension* extension = ext_service->GetExtensionByURL(origin); 582 if (extension) 583 return UTF8ToUTF16(extension->name()); 584 } 585 } 586 return UTF8ToUTF16(origin.host()); 587 } 588 589 void DesktopNotificationService::NotifySettingsChange() { 590 NotificationService::current()->Notify( 591 NotificationType::DESKTOP_NOTIFICATION_SETTINGS_CHANGED, 592 Source<DesktopNotificationService>(this), 593 NotificationService::NoDetails()); 594 } 595