blob: 585ac4ac701b35a8d1803d509a98833b47abdb65 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/notifications/desktop_notification_service.h"
#include "base/bind.h"
#include "base/metrics/histogram.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/notifications/desktop_notification_profile_util.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/notifications/notification_object_proxy.h"
#include "chrome/browser/notifications/notification_ui_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/pref_names.h"
#include "components/content_settings/core/common/permission_request_id.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_notification_delegate.h"
#include "content/public/common/show_desktop_notification_params.h"
#include "ui/base/webui/web_ui_util.h"
#include "ui/message_center/notifier_settings.h"
#if defined(ENABLE_EXTENSIONS)
#include "chrome/browser/extensions/api/notifications/notifications_api.h"
#include "chrome/browser/extensions/extension_service.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_system.h"
#include "extensions/browser/extension_util.h"
#include "extensions/browser/info_map.h"
#include "extensions/browser/suggest_permission_util.h"
#include "extensions/common/constants.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_set.h"
#endif
using content::BrowserThread;
using message_center::NotifierId;
namespace {
void CancelNotification(const std::string& id, ProfileID profile_id) {
g_browser_process->notification_ui_manager()->CancelById(id, profile_id);
}
} // namespace
// DesktopNotificationService -------------------------------------------------
// static
void DesktopNotificationService::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterListPref(
prefs::kMessageCenterDisabledExtensionIds,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
registry->RegisterListPref(
prefs::kMessageCenterDisabledSystemComponentIds,
user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
}
// static
std::string DesktopNotificationService::AddIconNotification(
const GURL& origin_url,
const base::string16& title,
const base::string16& message,
const gfx::Image& icon,
const base::string16& replace_id,
NotificationDelegate* delegate,
Profile* profile) {
Notification notification(message_center::NOTIFICATION_TYPE_SIMPLE,
origin_url,
title,
message,
icon,
blink::WebTextDirectionDefault,
NotifierId(origin_url),
base::string16(),
replace_id,
message_center::RichNotificationData(),
delegate);
g_browser_process->notification_ui_manager()->Add(notification, profile);
return notification.delegate_id();
}
DesktopNotificationService::DesktopNotificationService(Profile* profile)
: PermissionContextBase(profile, CONTENT_SETTINGS_TYPE_NOTIFICATIONS),
profile_(profile)
#if defined(ENABLE_EXTENSIONS)
,
extension_registry_observer_(this)
#endif
{
OnStringListPrefChanged(
prefs::kMessageCenterDisabledExtensionIds, &disabled_extension_ids_);
OnStringListPrefChanged(
prefs::kMessageCenterDisabledSystemComponentIds,
&disabled_system_component_ids_);
disabled_extension_id_pref_.Init(
prefs::kMessageCenterDisabledExtensionIds,
profile_->GetPrefs(),
base::Bind(
&DesktopNotificationService::OnStringListPrefChanged,
base::Unretained(this),
base::Unretained(prefs::kMessageCenterDisabledExtensionIds),
base::Unretained(&disabled_extension_ids_)));
disabled_system_component_id_pref_.Init(
prefs::kMessageCenterDisabledSystemComponentIds,
profile_->GetPrefs(),
base::Bind(
&DesktopNotificationService::OnStringListPrefChanged,
base::Unretained(this),
base::Unretained(prefs::kMessageCenterDisabledSystemComponentIds),
base::Unretained(&disabled_system_component_ids_)));
#if defined(ENABLE_EXTENSIONS)
extension_registry_observer_.Add(
extensions::ExtensionRegistry::Get(profile_));
#endif
}
DesktopNotificationService::~DesktopNotificationService() {
}
void DesktopNotificationService::RequestNotificationPermission(
content::WebContents* web_contents,
const PermissionRequestID& request_id,
const GURL& requesting_origin,
bool user_gesture,
const base::Callback<void(bool)>& result_callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
#if defined(ENABLE_EXTENSIONS)
extensions::InfoMap* extension_info_map =
extensions::ExtensionSystem::Get(profile_)->info_map();
const extensions::Extension* extension = NULL;
if (extension_info_map) {
extensions::ExtensionSet extensions;
extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
requesting_origin,
request_id.render_process_id(),
extensions::APIPermission::kNotifications,
&extensions);
for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
iter != extensions.end(); ++iter) {
if (IsNotifierEnabled(NotifierId(
NotifierId::APPLICATION, (*iter)->id()))) {
extension = iter->get();
break;
}
}
}
if (IsExtensionWithPermissionOrSuggestInConsole(
extensions::APIPermission::kNotifications,
extension,
web_contents->GetRenderViewHost())) {
result_callback.Run(true);
return;
}
#endif
RequestPermission(web_contents,
request_id,
requesting_origin,
user_gesture,
result_callback);
}
void DesktopNotificationService::ShowDesktopNotification(
const content::ShowDesktopNotificationHostMsgParams& params,
int render_process_id,
scoped_ptr<content::DesktopNotificationDelegate> delegate,
base::Closure* cancel_callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
const GURL& origin = params.origin;
NotificationObjectProxy* proxy = new NotificationObjectProxy(delegate.Pass());
base::string16 display_source = DisplayNameForOriginInProcessId(
origin, render_process_id);
// TODO(peter): Icons for Web Notifications are currently always requested for
// 1x scale, whereas the displays on which they can be displayed can have a
// different pixel density. Be smarter about this when the API gets updated
// with a way for developers to specify images of different resolutions.
Notification notification(origin, params.title, params.body,
gfx::Image::CreateFrom1xBitmap(params.icon),
display_source, params.replace_id, proxy);
// The webkit notification doesn't timeout.
notification.set_never_timeout(true);
g_browser_process->notification_ui_manager()->Add(notification, profile_);
if (cancel_callback)
*cancel_callback =
base::Bind(&CancelNotification,
proxy->id(),
NotificationUIManager::GetProfileID(profile_));
DesktopNotificationProfileUtil::UsePermission(profile_, origin);
}
base::string16 DesktopNotificationService::DisplayNameForOriginInProcessId(
const GURL& origin, int process_id) {
#if defined(ENABLE_EXTENSIONS)
// If the source is an extension, lookup the display name.
if (origin.SchemeIs(extensions::kExtensionScheme)) {
extensions::InfoMap* extension_info_map =
extensions::ExtensionSystem::Get(profile_)->info_map();
if (extension_info_map) {
extensions::ExtensionSet extensions;
extension_info_map->GetExtensionsWithAPIPermissionForSecurityOrigin(
origin,
process_id,
extensions::APIPermission::kNotifications,
&extensions);
for (extensions::ExtensionSet::const_iterator iter = extensions.begin();
iter != extensions.end(); ++iter) {
NotifierId notifier_id(NotifierId::APPLICATION, (*iter)->id());
if (IsNotifierEnabled(notifier_id))
return base::UTF8ToUTF16((*iter)->name());
}
}
}
#endif
return base::UTF8ToUTF16(origin.host());
}
bool DesktopNotificationService::IsNotifierEnabled(
const NotifierId& notifier_id) {
switch (notifier_id.type) {
case NotifierId::APPLICATION:
return disabled_extension_ids_.find(notifier_id.id) ==
disabled_extension_ids_.end();
case NotifierId::WEB_PAGE:
return DesktopNotificationProfileUtil::GetContentSetting(
profile_, notifier_id.url) == CONTENT_SETTING_ALLOW;
case NotifierId::SYSTEM_COMPONENT:
#if defined(OS_CHROMEOS)
return disabled_system_component_ids_.find(notifier_id.id) ==
disabled_system_component_ids_.end();
#else
// We do not disable system component notifications.
return true;
#endif
}
NOTREACHED();
return false;
}
void DesktopNotificationService::SetNotifierEnabled(
const NotifierId& notifier_id,
bool enabled) {
DCHECK_NE(NotifierId::WEB_PAGE, notifier_id.type);
bool add_new_item = false;
const char* pref_name = NULL;
scoped_ptr<base::StringValue> id;
switch (notifier_id.type) {
case NotifierId::APPLICATION:
pref_name = prefs::kMessageCenterDisabledExtensionIds;
add_new_item = !enabled;
id.reset(new base::StringValue(notifier_id.id));
FirePermissionLevelChangedEvent(notifier_id, enabled);
break;
case NotifierId::SYSTEM_COMPONENT:
#if defined(OS_CHROMEOS)
pref_name = prefs::kMessageCenterDisabledSystemComponentIds;
add_new_item = !enabled;
id.reset(new base::StringValue(notifier_id.id));
#else
return;
#endif
break;
default:
NOTREACHED();
}
DCHECK(pref_name != NULL);
ListPrefUpdate update(profile_->GetPrefs(), pref_name);
base::ListValue* const list = update.Get();
if (add_new_item) {
// AppendIfNotPresent will delete |adding_value| when the same value
// already exists.
list->AppendIfNotPresent(id.release());
} else {
list->Remove(*id, NULL);
}
}
void DesktopNotificationService::OnStringListPrefChanged(
const char* pref_name, std::set<std::string>* ids_field) {
ids_field->clear();
// Separate GetPrefs()->GetList() to analyze the crash. See crbug.com/322320
const PrefService* pref_service = profile_->GetPrefs();
CHECK(pref_service);
const base::ListValue* pref_list = pref_service->GetList(pref_name);
for (size_t i = 0; i < pref_list->GetSize(); ++i) {
std::string element;
if (pref_list->GetString(i, &element) && !element.empty())
ids_field->insert(element);
else
LOG(WARNING) << i << "-th element is not a string for " << pref_name;
}
}
#if defined(ENABLE_EXTENSIONS)
void DesktopNotificationService::OnExtensionUninstalled(
content::BrowserContext* browser_context,
const extensions::Extension* extension,
extensions::UninstallReason reason) {
NotifierId notifier_id(NotifierId::APPLICATION, extension->id());
if (IsNotifierEnabled(notifier_id))
return;
// The settings for ephemeral apps will be persisted across cache evictions.
if (extensions::util::IsEphemeralApp(extension->id(), profile_))
return;
SetNotifierEnabled(notifier_id, true);
}
#endif
// Unlike other permission types, granting a notification for a given origin
// will not take into account the |embedder_origin|, it will only be based
// on the requesting iframe origin.
// TODO(mukai) Consider why notifications behave differently than
// other permissions. crbug.com/416894
void DesktopNotificationService::UpdateContentSetting(
const GURL& requesting_origin,
const GURL& embedder_origin,
bool allowed) {
if (allowed) {
DesktopNotificationProfileUtil::GrantPermission(
profile_, requesting_origin);
} else {
DesktopNotificationProfileUtil::DenyPermission(profile_, requesting_origin);
}
}
void DesktopNotificationService::FirePermissionLevelChangedEvent(
const NotifierId& notifier_id, bool enabled) {
#if defined(ENABLE_EXTENSIONS)
DCHECK_EQ(NotifierId::APPLICATION, notifier_id.type);
extensions::api::notifications::PermissionLevel permission =
enabled ? extensions::api::notifications::PERMISSION_LEVEL_GRANTED
: extensions::api::notifications::PERMISSION_LEVEL_DENIED;
scoped_ptr<base::ListValue> args(new base::ListValue());
args->Append(new base::StringValue(
extensions::api::notifications::ToString(permission)));
scoped_ptr<extensions::Event> event(new extensions::Event(
extensions::api::notifications::OnPermissionLevelChanged::kEventName,
args.Pass()));
extensions::EventRouter::Get(profile_)
->DispatchEventToExtension(notifier_id.id, event.Pass());
// Tell the IO thread that this extension's permission for notifications
// has changed.
extensions::InfoMap* extension_info_map =
extensions::ExtensionSystem::Get(profile_)->info_map();
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&extensions::InfoMap::SetNotificationsDisabled,
extension_info_map, notifier_id.id, !enabled));
#endif
}