desktop_notification_service.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
1// Copyright (c) 2010 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 "app/l10n_util.h" 8#include "app/resource_bundle.h" 9#include "base/metrics/histogram.h" 10#include "base/thread.h" 11#include "base/utf_string_conversions.h" 12#include "chrome/browser/browser_child_process_host.h" 13#include "chrome/browser/browser_thread.h" 14#include "chrome/browser/extensions/extensions_service.h" 15#include "chrome/browser/notifications/notification.h" 16#include "chrome/browser/notifications/notification_object_proxy.h" 17#include "chrome/browser/notifications/notification_ui_manager.h" 18#include "chrome/browser/notifications/notifications_prefs_cache.h" 19#include "chrome/browser/prefs/pref_service.h" 20#include "chrome/browser/prefs/scoped_pref_update.h" 21#include "chrome/browser/profile.h" 22#include "chrome/browser/renderer_host/render_process_host.h" 23#include "chrome/browser/renderer_host/render_view_host.h" 24#include "chrome/browser/renderer_host/site_instance.h" 25#include "chrome/browser/tab_contents/infobar_delegate.h" 26#include "chrome/browser/tab_contents/tab_contents.h" 27#include "chrome/browser/worker_host/worker_process_host.h" 28#include "chrome/common/notification_service.h" 29#include "chrome/common/notification_type.h" 30#include "chrome/common/pref_names.h" 31#include "chrome/common/render_messages.h" 32#include "chrome/common/render_messages_params.h" 33#include "chrome/common/url_constants.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/WebKit/chromium/public/WebNotificationPresenter.h" 40 41using WebKit::WebNotificationPresenter; 42using WebKit::WebTextDirection; 43 44const ContentSetting kDefaultSetting = CONTENT_SETTING_ASK; 45 46// static 47string16 DesktopNotificationService::CreateDataUrl( 48 const GURL& icon_url, const string16& title, const string16& body, 49 WebTextDirection dir) { 50 int resource; 51 std::vector<std::string> subst; 52 if (icon_url.is_valid()) { 53 resource = IDR_NOTIFICATION_ICON_HTML; 54 subst.push_back(icon_url.spec()); 55 subst.push_back(EscapeForHTML(UTF16ToUTF8(title))); 56 subst.push_back(EscapeForHTML(UTF16ToUTF8(body))); 57 // icon float position 58 subst.push_back(dir == WebKit::WebTextDirectionRightToLeft ? 59 "right" : "left"); 60 } else if (title.empty() || body.empty()) { 61 resource = IDR_NOTIFICATION_1LINE_HTML; 62 string16 line = title.empty() ? body : title; 63 // Strings are div names in the template file. 64 string16 line_name = title.empty() ? ASCIIToUTF16("description") 65 : ASCIIToUTF16("title"); 66 subst.push_back(EscapeForHTML(UTF16ToUTF8(line_name))); 67 subst.push_back(EscapeForHTML(UTF16ToUTF8(line))); 68 } else { 69 resource = IDR_NOTIFICATION_2LINE_HTML; 70 subst.push_back(EscapeForHTML(UTF16ToUTF8(title))); 71 subst.push_back(EscapeForHTML(UTF16ToUTF8(body))); 72 } 73 // body text direction 74 subst.push_back(dir == WebKit::WebTextDirectionRightToLeft ? 75 "rtl" : "ltr"); 76 77 return CreateDataUrl(resource, subst); 78} 79 80// static 81string16 DesktopNotificationService::CreateDataUrl( 82 int resource, const std::vector<std::string>& subst) { 83 const base::StringPiece template_html( 84 ResourceBundle::GetSharedInstance().GetRawDataResource( 85 resource)); 86 87 if (template_html.empty()) { 88 NOTREACHED() << "unable to load template. ID: " << resource; 89 return string16(); 90 } 91 92 std::string data = ReplaceStringPlaceholders(template_html, subst, NULL); 93 return UTF8ToUTF16("data:text/html;charset=utf-8," + 94 EscapeQueryParamValue(data, false)); 95} 96 97// A task object which calls the renderer to inform the web page that the 98// permission request has completed. 99class NotificationPermissionCallbackTask : public Task { 100 public: 101 NotificationPermissionCallbackTask(int process_id, int route_id, 102 int request_id) 103 : process_id_(process_id), 104 route_id_(route_id), 105 request_id_(request_id) { 106 } 107 108 virtual void Run() { 109 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 110 RenderViewHost* host = RenderViewHost::FromID(process_id_, route_id_); 111 if (host) 112 host->Send(new ViewMsg_PermissionRequestDone(route_id_, request_id_)); 113 } 114 115 private: 116 int process_id_; 117 int route_id_; 118 int request_id_; 119}; 120 121// The delegate for the infobar shown when an origin requests notification 122// permissions. 123class NotificationPermissionInfoBarDelegate : public ConfirmInfoBarDelegate { 124 public: 125 NotificationPermissionInfoBarDelegate(TabContents* contents, 126 const GURL& origin, 127 const string16& display_name, 128 int process_id, 129 int route_id, 130 int callback_context) 131 : ConfirmInfoBarDelegate(contents), 132 origin_(origin), 133 display_name_(display_name), 134 profile_(contents->profile()), 135 process_id_(process_id), 136 route_id_(route_id), 137 callback_context_(callback_context), 138 action_taken_(false) { 139 } 140 141 // Overridden from ConfirmInfoBarDelegate: 142 virtual void InfoBarClosed() { 143 if (!action_taken_) 144 UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Ignored", 1); 145 146 BrowserThread::PostTask( 147 BrowserThread::IO, FROM_HERE, 148 new NotificationPermissionCallbackTask( 149 process_id_, route_id_, callback_context_)); 150 151 delete this; 152 } 153 154 virtual string16 GetMessageText() const { 155 return l10n_util::GetStringFUTF16(IDS_NOTIFICATION_PERMISSIONS, 156 display_name_); 157 } 158 159 virtual SkBitmap* GetIcon() const { 160 return ResourceBundle::GetSharedInstance().GetBitmapNamed( 161 IDR_PRODUCT_ICON_32); 162 } 163 164 virtual int GetButtons() const { 165 return BUTTON_OK | BUTTON_CANCEL | BUTTON_OK_DEFAULT; 166 } 167 168 virtual string16 GetButtonLabel(InfoBarButton button) const { 169 return button == BUTTON_OK ? 170 l10n_util::GetStringUTF16(IDS_NOTIFICATION_PERMISSION_YES) : 171 l10n_util::GetStringUTF16(IDS_NOTIFICATION_PERMISSION_NO); 172 } 173 174 virtual bool Accept() { 175 UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Allowed", 1); 176 profile_->GetDesktopNotificationService()->GrantPermission(origin_); 177 action_taken_ = true; 178 return true; 179 } 180 181 virtual bool Cancel() { 182 UMA_HISTOGRAM_COUNTS("NotificationPermissionRequest.Denied", 1); 183 profile_->GetDesktopNotificationService()->DenyPermission(origin_); 184 action_taken_ = true; 185 return true; 186 } 187 188 // Overridden from InfoBarDelegate: 189 virtual Type GetInfoBarType() { return PAGE_ACTION_TYPE; } 190 191 private: 192 // The origin we are asking for permissions on. 193 GURL origin_; 194 195 // The display name for the origin to be displayed. Will be different from 196 // origin_ for extensions. 197 string16 display_name_; 198 199 // The Profile that we restore sessions from. 200 Profile* profile_; 201 202 // The callback information that tells us how to respond to javascript via 203 // the correct RenderView. 204 int process_id_; 205 int route_id_; 206 int callback_context_; 207 208 // Whether the user clicked one of the buttons. 209 bool action_taken_; 210 211 DISALLOW_COPY_AND_ASSIGN(NotificationPermissionInfoBarDelegate); 212}; 213 214DesktopNotificationService::DesktopNotificationService(Profile* profile, 215 NotificationUIManager* ui_manager) 216 : profile_(profile), 217 ui_manager_(ui_manager) { 218 registrar_.Init(profile_->GetPrefs()); 219 InitPrefs(); 220 StartObserving(); 221} 222 223DesktopNotificationService::~DesktopNotificationService() { 224 StopObserving(); 225} 226 227void DesktopNotificationService::RegisterUserPrefs(PrefService* user_prefs) { 228 if (!user_prefs->FindPreference( 229 prefs::kDesktopNotificationDefaultContentSetting)) { 230 user_prefs->RegisterIntegerPref( 231 prefs::kDesktopNotificationDefaultContentSetting, kDefaultSetting); 232 } 233 if (!user_prefs->FindPreference(prefs::kDesktopNotificationAllowedOrigins)) 234 user_prefs->RegisterListPref(prefs::kDesktopNotificationAllowedOrigins); 235 if (!user_prefs->FindPreference(prefs::kDesktopNotificationDeniedOrigins)) 236 user_prefs->RegisterListPref(prefs::kDesktopNotificationDeniedOrigins); 237} 238 239// Initialize the cache with the allowed and denied origins, or 240// create the preferences if they don't exist yet. 241void DesktopNotificationService::InitPrefs() { 242 PrefService* prefs = profile_->GetPrefs(); 243 std::vector<GURL> allowed_origins; 244 std::vector<GURL> denied_origins; 245 ContentSetting default_content_setting = CONTENT_SETTING_DEFAULT; 246 247 if (!profile_->IsOffTheRecord()) { 248 default_content_setting = IntToContentSetting( 249 prefs->GetInteger(prefs::kDesktopNotificationDefaultContentSetting)); 250 allowed_origins = GetAllowedOrigins(); 251 denied_origins = GetBlockedOrigins(); 252 } 253 254 prefs_cache_ = new NotificationsPrefsCache(); 255 prefs_cache_->SetCacheDefaultContentSetting(default_content_setting); 256 prefs_cache_->SetCacheAllowedOrigins(allowed_origins); 257 prefs_cache_->SetCacheDeniedOrigins(denied_origins); 258 prefs_cache_->set_is_initialized(true); 259} 260 261void DesktopNotificationService::StartObserving() { 262 if (!profile_->IsOffTheRecord()) { 263 registrar_.Add(prefs::kDesktopNotificationDefaultContentSetting, this); 264 registrar_.Add(prefs::kDesktopNotificationAllowedOrigins, this); 265 registrar_.Add(prefs::kDesktopNotificationDeniedOrigins, this); 266 } 267} 268 269void DesktopNotificationService::StopObserving() { 270 if (!profile_->IsOffTheRecord()) { 271 registrar_.RemoveAll(); 272 } 273} 274 275void DesktopNotificationService::GrantPermission(const GURL& origin) { 276 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 277 PersistPermissionChange(origin, true); 278 279 // Schedule a cache update on the IO thread. 280 BrowserThread::PostTask( 281 BrowserThread::IO, FROM_HERE, 282 NewRunnableMethod( 283 prefs_cache_.get(), &NotificationsPrefsCache::CacheAllowedOrigin, 284 origin)); 285} 286 287void DesktopNotificationService::DenyPermission(const GURL& origin) { 288 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 289 PersistPermissionChange(origin, false); 290 291 // Schedule a cache update on the IO thread. 292 BrowserThread::PostTask( 293 BrowserThread::IO, FROM_HERE, 294 NewRunnableMethod( 295 prefs_cache_.get(), &NotificationsPrefsCache::CacheDeniedOrigin, 296 origin)); 297} 298 299void DesktopNotificationService::Observe(NotificationType type, 300 const NotificationSource& source, 301 const NotificationDetails& details) { 302 DCHECK(NotificationType::PREF_CHANGED == type); 303 PrefService* prefs = profile_->GetPrefs(); 304 const std::string& name = *Details<std::string>(details).ptr(); 305 306 if (name == prefs::kDesktopNotificationAllowedOrigins) { 307 NotificationService::current()->Notify( 308 NotificationType::DESKTOP_NOTIFICATION_SETTINGS_CHANGED, 309 Source<DesktopNotificationService>(this), 310 NotificationService::NoDetails()); 311 312 std::vector<GURL> allowed_origins(GetAllowedOrigins()); 313 // Schedule a cache update on the IO thread. 314 BrowserThread::PostTask( 315 BrowserThread::IO, FROM_HERE, 316 NewRunnableMethod( 317 prefs_cache_.get(), 318 &NotificationsPrefsCache::SetCacheAllowedOrigins, 319 allowed_origins)); 320 } else if (name == prefs::kDesktopNotificationDeniedOrigins) { 321 NotificationService::current()->Notify( 322 NotificationType::DESKTOP_NOTIFICATION_SETTINGS_CHANGED, 323 Source<DesktopNotificationService>(this), 324 NotificationService::NoDetails()); 325 326 std::vector<GURL> denied_origins(GetBlockedOrigins()); 327 // Schedule a cache update on the IO thread. 328 BrowserThread::PostTask( 329 BrowserThread::IO, FROM_HERE, 330 NewRunnableMethod( 331 prefs_cache_.get(), 332 &NotificationsPrefsCache::SetCacheDeniedOrigins, 333 denied_origins)); 334 } else if (name == prefs::kDesktopNotificationDefaultContentSetting) { 335 NotificationService::current()->Notify( 336 NotificationType::DESKTOP_NOTIFICATION_DEFAULT_CHANGED, 337 Source<DesktopNotificationService>(this), 338 NotificationService::NoDetails()); 339 340 const ContentSetting default_content_setting = IntToContentSetting( 341 prefs->GetInteger(prefs::kDesktopNotificationDefaultContentSetting)); 342 343 // Schedule a cache update on the IO thread. 344 BrowserThread::PostTask( 345 BrowserThread::IO, FROM_HERE, 346 NewRunnableMethod( 347 prefs_cache_.get(), 348 &NotificationsPrefsCache::SetCacheDefaultContentSetting, 349 default_content_setting)); 350 } 351} 352 353void DesktopNotificationService::PersistPermissionChange( 354 const GURL& origin, bool is_allowed) { 355 // Don't persist changes when off the record. 356 if (profile_->IsOffTheRecord()) 357 return; 358 359 PrefService* prefs = profile_->GetPrefs(); 360 361 // |Observe()| updates the whole permission set in the cache, but only a 362 // single origin has changed. Hence, callers of this method manually 363 // schedule a task to update the prefs cache, and the prefs observer is 364 // disabled while the update runs. 365 StopObserving(); 366 367 bool allowed_changed = false; 368 bool denied_changed = false; 369 370 ListValue* allowed_sites = 371 prefs->GetMutableList(prefs::kDesktopNotificationAllowedOrigins); 372 ListValue* denied_sites = 373 prefs->GetMutableList(prefs::kDesktopNotificationDeniedOrigins); 374 { 375 // value is passed to the preferences list, or deleted. 376 StringValue* value = new StringValue(origin.spec()); 377 378 // Remove from one list and add to the other. 379 if (is_allowed) { 380 // Remove from the denied list. 381 if (denied_sites->Remove(*value) != -1) 382 denied_changed = true; 383 384 // Add to the allowed list. 385 if (allowed_sites->AppendIfNotPresent(value)) 386 allowed_changed = true; 387 else 388 delete value; 389 } else { 390 // Remove from the allowed list. 391 if (allowed_sites->Remove(*value) != -1) 392 allowed_changed = true; 393 394 // Add to the denied list. 395 if (denied_sites->AppendIfNotPresent(value)) 396 denied_changed = true; 397 else 398 delete value; 399 } 400 } 401 402 // Persist the pref if anthing changed, but only send updates for the 403 // list that changed. 404 if (allowed_changed || denied_changed) { 405 if (allowed_changed) { 406 ScopedPrefUpdate update_allowed( 407 prefs, prefs::kDesktopNotificationAllowedOrigins); 408 } 409 if (denied_changed) { 410 ScopedPrefUpdate updateDenied( 411 prefs, prefs::kDesktopNotificationDeniedOrigins); 412 } 413 prefs->ScheduleSavePersistentPrefs(); 414 } 415 StartObserving(); 416} 417 418ContentSetting DesktopNotificationService::GetDefaultContentSetting() { 419 PrefService* prefs = profile_->GetPrefs(); 420 ContentSetting setting = IntToContentSetting( 421 prefs->GetInteger(prefs::kDesktopNotificationDefaultContentSetting)); 422 if (setting == CONTENT_SETTING_DEFAULT) 423 setting = kDefaultSetting; 424 return setting; 425} 426 427void DesktopNotificationService::SetDefaultContentSetting( 428 ContentSetting setting) { 429 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 430 profile_->GetPrefs()->SetInteger( 431 prefs::kDesktopNotificationDefaultContentSetting, 432 setting == CONTENT_SETTING_DEFAULT ? kDefaultSetting : setting); 433 // The cache is updated through the notification observer. 434} 435 436void DesktopNotificationService::ResetToDefaultContentSetting() { 437 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 438 439 PrefService* prefs = profile_->GetPrefs(); 440 prefs->ClearPref(prefs::kDesktopNotificationDefaultContentSetting); 441} 442 443std::vector<GURL> DesktopNotificationService::GetAllowedOrigins() { 444 std::vector<GURL> allowed_origins; 445 PrefService* prefs = profile_->GetPrefs(); 446 const ListValue* allowed_sites = 447 prefs->GetList(prefs::kDesktopNotificationAllowedOrigins); 448 if (allowed_sites) { 449 NotificationsPrefsCache::ListValueToGurlVector(*allowed_sites, 450 &allowed_origins); 451 } 452 return allowed_origins; 453} 454 455std::vector<GURL> DesktopNotificationService::GetBlockedOrigins() { 456 std::vector<GURL> denied_origins; 457 PrefService* prefs = profile_->GetPrefs(); 458 const ListValue* denied_sites = 459 prefs->GetList(prefs::kDesktopNotificationDeniedOrigins); 460 if (denied_sites) { 461 NotificationsPrefsCache::ListValueToGurlVector(*denied_sites, 462 &denied_origins); 463 } 464 return denied_origins; 465} 466 467void DesktopNotificationService::ResetAllowedOrigin(const GURL& origin) { 468 if (profile_->IsOffTheRecord()) 469 return; 470 471 // Since this isn't called often, let the normal observer behavior update the 472 // cache in this case. 473 PrefService* prefs = profile_->GetPrefs(); 474 ListValue* allowed_sites = 475 prefs->GetMutableList(prefs::kDesktopNotificationAllowedOrigins); 476 { 477 StringValue value(origin.spec()); 478 int removed_index = allowed_sites->Remove(value); 479 DCHECK_NE(-1, removed_index) << origin << " was not allowed"; 480 ScopedPrefUpdate update_allowed( 481 prefs, prefs::kDesktopNotificationAllowedOrigins); 482 } 483 prefs->ScheduleSavePersistentPrefs(); 484} 485 486void DesktopNotificationService::ResetBlockedOrigin(const GURL& origin) { 487 if (profile_->IsOffTheRecord()) 488 return; 489 490 // Since this isn't called often, let the normal observer behavior update the 491 // cache in this case. 492 PrefService* prefs = profile_->GetPrefs(); 493 ListValue* denied_sites = 494 prefs->GetMutableList(prefs::kDesktopNotificationDeniedOrigins); 495 { 496 StringValue value(origin.spec()); 497 int removed_index = denied_sites->Remove(value); 498 DCHECK_NE(-1, removed_index) << origin << " was not blocked"; 499 ScopedPrefUpdate update_allowed( 500 prefs, prefs::kDesktopNotificationDeniedOrigins); 501 } 502 prefs->ScheduleSavePersistentPrefs(); 503} 504 505void DesktopNotificationService::ResetAllOrigins() { 506 PrefService* prefs = profile_->GetPrefs(); 507 prefs->ClearPref(prefs::kDesktopNotificationAllowedOrigins); 508 prefs->ClearPref(prefs::kDesktopNotificationDeniedOrigins); 509} 510 511ContentSetting DesktopNotificationService::GetContentSetting( 512 const GURL& origin) { 513 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 514 if (profile_->IsOffTheRecord()) 515 return kDefaultSetting; 516 517 std::vector<GURL> allowed_origins(GetAllowedOrigins()); 518 if (std::find(allowed_origins.begin(), allowed_origins.end(), origin) != 519 allowed_origins.end()) 520 return CONTENT_SETTING_ALLOW; 521 522 std::vector<GURL> denied_origins(GetBlockedOrigins()); 523 if (std::find(denied_origins.begin(), denied_origins.end(), origin) != 524 denied_origins.end()) 525 return CONTENT_SETTING_BLOCK; 526 527 return GetDefaultContentSetting(); 528} 529 530void DesktopNotificationService::RequestPermission( 531 const GURL& origin, int process_id, int route_id, int callback_context, 532 TabContents* tab) { 533 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 534 if (!tab) 535 return; 536 537 // If |origin| hasn't been seen before and the default content setting for 538 // notifications is "ask", show an infobar. 539 // The cache can only answer queries on the IO thread once it's initialized, 540 // so don't ask the cache. 541 ContentSetting setting = GetContentSetting(origin); 542 if (setting == CONTENT_SETTING_ASK) { 543 // Show an info bar requesting permission. 544 tab->AddInfoBar(new NotificationPermissionInfoBarDelegate( 545 tab, origin, DisplayNameForOrigin(origin), process_id, 546 route_id, callback_context)); 547 } else { 548 // Notify renderer immediately. 549 BrowserThread::PostTask( 550 BrowserThread::IO, FROM_HERE, 551 new NotificationPermissionCallbackTask( 552 process_id, route_id, callback_context)); 553 } 554} 555 556void DesktopNotificationService::ShowNotification( 557 const Notification& notification) { 558 ui_manager_->Add(notification, profile_); 559} 560 561bool DesktopNotificationService::CancelDesktopNotification( 562 int process_id, int route_id, int notification_id) { 563 scoped_refptr<NotificationObjectProxy> proxy( 564 new NotificationObjectProxy(process_id, route_id, notification_id, 565 false)); 566 // TODO(johnnyg): clean up this "empty" notification. 567 Notification notif(GURL(), GURL(), string16(), string16(), proxy); 568 return ui_manager_->Cancel(notif); 569} 570 571 572bool DesktopNotificationService::ShowDesktopNotification( 573 const ViewHostMsg_ShowNotification_Params& params, 574 int process_id, int route_id, DesktopNotificationSource source) { 575 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 576 const GURL& origin = params.origin; 577 NotificationObjectProxy* proxy = 578 new NotificationObjectProxy(process_id, route_id, 579 params.notification_id, 580 source == WorkerNotification); 581 GURL contents; 582 if (params.is_html) { 583 contents = params.contents_url; 584 } else { 585 // "upconvert" the string parameters to a data: URL. 586 contents = GURL( 587 CreateDataUrl(params.icon_url, params.title, params.body, 588 params.direction)); 589 } 590 Notification notification( 591 origin, contents, DisplayNameForOrigin(origin), 592 params.replace_id, proxy); 593 ShowNotification(notification); 594 return true; 595} 596 597string16 DesktopNotificationService::DisplayNameForOrigin( 598 const GURL& origin) { 599 // If the source is an extension, lookup the display name. 600 if (origin.SchemeIs(chrome::kExtensionScheme)) { 601 ExtensionsService* ext_service = profile_->GetExtensionsService(); 602 if (ext_service) { 603 Extension* extension = ext_service->GetExtensionByURL(origin); 604 if (extension) 605 return UTF8ToUTF16(extension->name()); 606 } 607 } 608 return UTF8ToUTF16(origin.host()); 609} 610