Home | History | Annotate | Download | only in notifications
      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