// Copyright (c) 2011 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/content_settings/content_settings_notification_provider.h"
#include "base/string_util.h"
#include "chrome/browser/notifications/desktop_notification_service_factory.h"
#include "chrome/browser/notifications/notification.h"
#include "chrome/browser/notifications/notifications_prefs_cache.h"
#include "chrome/browser/notifications/notification_ui_manager.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/prefs/scoped_user_pref_update.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/content_settings_types.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/url_constants.h"
#include "content/common/notification_service.h"
#include "content/common/notification_type.h"
#include "googleurl/src/gurl.h"
namespace {
const ContentSetting kDefaultSetting = CONTENT_SETTING_ASK;
} // namespace
namespace content_settings {
// ////////////////////////////////////////////////////////////////////////////
// NotificationProvider
//
// static
void NotificationProvider::RegisterUserPrefs(PrefService* user_prefs) {
if (!user_prefs->FindPreference(prefs::kDesktopNotificationAllowedOrigins))
user_prefs->RegisterListPref(prefs::kDesktopNotificationAllowedOrigins);
if (!user_prefs->FindPreference(prefs::kDesktopNotificationDeniedOrigins))
user_prefs->RegisterListPref(prefs::kDesktopNotificationDeniedOrigins);
}
// TODO(markusheintz): Re-factoring in progress. Do not move or touch the
// following two static methods as you might cause trouble. Thanks!
// static
ContentSettingsPattern NotificationProvider::ToContentSettingsPattern(
const GURL& origin) {
// Fix empty GURLs.
if (origin.spec().empty()) {
std::string pattern_spec(chrome::kFileScheme);
pattern_spec += chrome::kStandardSchemeSeparator;
return ContentSettingsPattern(pattern_spec);
}
return ContentSettingsPattern::FromURLNoWildcard(origin);
}
// static
GURL NotificationProvider::ToGURL(const ContentSettingsPattern& pattern) {
std::string pattern_spec(pattern.AsString());
if (pattern_spec.empty() ||
StartsWithASCII(pattern_spec,
std::string(ContentSettingsPattern::kDomainWildcard),
true)) {
NOTREACHED();
}
std::string url_spec("");
if (StartsWithASCII(pattern_spec, std::string(chrome::kFileScheme), false)) {
url_spec += pattern_spec;
} else if (!pattern.scheme().empty()) {
url_spec += pattern.scheme();
url_spec += chrome::kStandardSchemeSeparator;
url_spec += pattern_spec;
}
return GURL(url_spec);
}
NotificationProvider::NotificationProvider(
Profile* profile)
: profile_(profile) {
prefs_registrar_.Init(profile_->GetPrefs());
StartObserving();
}
NotificationProvider::~NotificationProvider() {
StopObserving();
}
bool NotificationProvider::ContentSettingsTypeIsManaged(
ContentSettingsType content_type) {
return false;
}
ContentSetting NotificationProvider::GetContentSetting(
const GURL& requesting_url,
const GURL& embedding_url,
ContentSettingsType content_type,
const ResourceIdentifier& resource_identifier) const {
if (content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS)
return CONTENT_SETTING_DEFAULT;
return GetContentSetting(requesting_url);
}
void NotificationProvider::SetContentSetting(
const ContentSettingsPattern& requesting_url_pattern,
const ContentSettingsPattern& embedding_url_pattern,
ContentSettingsType content_type,
const ResourceIdentifier& resource_identifier,
ContentSetting content_setting) {
if (content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS)
return;
GURL origin = ToGURL(requesting_url_pattern);
if (CONTENT_SETTING_ALLOW == content_setting) {
GrantPermission(origin);
} else if (CONTENT_SETTING_BLOCK == content_setting) {
DenyPermission(origin);
} else if (CONTENT_SETTING_DEFAULT == content_setting) {
ContentSetting current_setting = GetContentSetting(origin);
if (CONTENT_SETTING_ALLOW == current_setting) {
ResetAllowedOrigin(origin);
} else if (CONTENT_SETTING_BLOCK == current_setting) {
ResetBlockedOrigin(origin);
} else {
NOTREACHED();
}
} else {
NOTREACHED();
}
}
void NotificationProvider::GetAllContentSettingsRules(
ContentSettingsType content_type,
const ResourceIdentifier& resource_identifier,
Rules* content_setting_rules) const {
if (content_type != CONTENT_SETTINGS_TYPE_NOTIFICATIONS)
return;
std::vector<GURL> allowed_origins = GetAllowedOrigins();
std::vector<GURL> denied_origins = GetBlockedOrigins();
for (std::vector<GURL>::iterator url = allowed_origins.begin();
url != allowed_origins.end();
++url) {
ContentSettingsPattern pattern =
ContentSettingsPattern::FromURLNoWildcard(*url);
content_setting_rules->push_back(Rule(
pattern,
pattern,
CONTENT_SETTING_ALLOW));
}
for (std::vector<GURL>::iterator url = denied_origins.begin();
url != denied_origins.end();
++url) {
ContentSettingsPattern pattern =
ContentSettingsPattern::FromURLNoWildcard(*url);
content_setting_rules->push_back(Rule(
pattern,
pattern,
CONTENT_SETTING_BLOCK));
}
}
void NotificationProvider::ClearAllContentSettingsRules(
ContentSettingsType content_type) {
if (content_type == CONTENT_SETTINGS_TYPE_NOTIFICATIONS)
ResetAllOrigins();
}
void NotificationProvider::ResetToDefaults() {
ResetAllOrigins();
}
void NotificationProvider::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
if (NotificationType::PREF_CHANGED == type) {
const std::string& name = *Details<std::string>(details).ptr();
OnPrefsChanged(name);
} else if (NotificationType::PROFILE_DESTROYED == type) {
StopObserving();
}
}
/////////////////////////////////////////////////////////////////////
// Private
//
void NotificationProvider::StartObserving() {
if (!profile_->IsOffTheRecord()) {
prefs_registrar_.Add(prefs::kDesktopNotificationDefaultContentSetting,
this);
prefs_registrar_.Add(prefs::kDesktopNotificationAllowedOrigins, this);
prefs_registrar_.Add(prefs::kDesktopNotificationDeniedOrigins, this);
notification_registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
NotificationService::AllSources());
}
notification_registrar_.Add(this, NotificationType::PROFILE_DESTROYED,
Source<Profile>(profile_));
}
void NotificationProvider::StopObserving() {
if (!profile_->IsOffTheRecord()) {
prefs_registrar_.RemoveAll();
}
notification_registrar_.RemoveAll();
}
void NotificationProvider::OnPrefsChanged(const std::string& pref_name) {
if (pref_name == prefs::kDesktopNotificationAllowedOrigins) {
NotifySettingsChange();
} else if (pref_name == prefs::kDesktopNotificationDeniedOrigins) {
NotifySettingsChange();
}
}
void NotificationProvider::NotifySettingsChange() {
// TODO(markusheintz): Re-factoring work in progress: Replace the
// DESKTOP_NOTIFICATION_SETTINGS_CHANGED with a CONTENT_SETTINGS_CHANGED
// notification, and use the HostContentSettingsMap as source once this
// content settings provider in integrated in the HostContentSetttingsMap.
NotificationService::current()->Notify(
NotificationType::DESKTOP_NOTIFICATION_SETTINGS_CHANGED,
Source<DesktopNotificationService>(
DesktopNotificationServiceFactory::GetForProfile(profile_)),
NotificationService::NoDetails());
}
std::vector<GURL> NotificationProvider::GetAllowedOrigins() const {
std::vector<GURL> allowed_origins;
PrefService* prefs = profile_->GetPrefs();
const ListValue* allowed_sites =
prefs->GetList(prefs::kDesktopNotificationAllowedOrigins);
if (allowed_sites) {
// TODO(markusheintz): Remove dependency to PrefsCache
NotificationsPrefsCache::ListValueToGurlVector(*allowed_sites,
&allowed_origins);
}
return allowed_origins;
}
std::vector<GURL> NotificationProvider::GetBlockedOrigins() const {
std::vector<GURL> denied_origins;
PrefService* prefs = profile_->GetPrefs();
const ListValue* denied_sites =
prefs->GetList(prefs::kDesktopNotificationDeniedOrigins);
if (denied_sites) {
// TODO(markusheintz): Remove dependency to PrefsCache
NotificationsPrefsCache::ListValueToGurlVector(*denied_sites,
&denied_origins);
}
return denied_origins;
}
void NotificationProvider::GrantPermission(const GURL& origin) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
PersistPermissionChange(origin, true);
NotifySettingsChange();
}
void NotificationProvider::DenyPermission(const GURL& origin) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
PersistPermissionChange(origin, false);
NotifySettingsChange();
}
void NotificationProvider::PersistPermissionChange(
const GURL& origin, bool is_allowed) {
// Don't persist changes when incognito.
if (profile_->IsOffTheRecord())
return;
PrefService* prefs = profile_->GetPrefs();
// |Observe()| updates the whole permission set in the cache, but only a
// single origin has changed. Hence, callers of this method manually
// schedule a task to update the prefs cache, and the prefs observer is
// disabled while the update runs.
StopObserving();
bool allowed_changed = false;
bool denied_changed = false;
{
ListPrefUpdate update_allowed_sites(
prefs, prefs::kDesktopNotificationAllowedOrigins);
ListPrefUpdate update_denied_sites(
prefs, prefs::kDesktopNotificationDeniedOrigins);
ListValue* allowed_sites = update_allowed_sites.Get();
ListValue* denied_sites = update_denied_sites.Get();
// |value| is passed to the preferences list, or deleted.
StringValue* value = new StringValue(origin.spec());
// Remove from one list and add to the other.
if (is_allowed) {
// Remove from the denied list.
if (denied_sites->Remove(*value) != -1)
denied_changed = true;
// Add to the allowed list.
if (allowed_sites->AppendIfNotPresent(value))
allowed_changed = true;
} else {
// Remove from the allowed list.
if (allowed_sites->Remove(*value) != -1)
allowed_changed = true;
// Add to the denied list.
if (denied_sites->AppendIfNotPresent(value))
denied_changed = true;
}
}
// Persist the pref if anthing changed, but only send updates for the
// list that changed.
if (allowed_changed || denied_changed)
prefs->ScheduleSavePersistentPrefs();
StartObserving();
}
ContentSetting NotificationProvider::GetContentSetting(
const GURL& origin) const {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
if (profile_->IsOffTheRecord())
return kDefaultSetting;
std::vector<GURL> allowed_origins(GetAllowedOrigins());
if (std::find(allowed_origins.begin(), allowed_origins.end(), origin) !=
allowed_origins.end())
return CONTENT_SETTING_ALLOW;
std::vector<GURL> denied_origins(GetBlockedOrigins());
if (std::find(denied_origins.begin(), denied_origins.end(), origin) !=
denied_origins.end())
return CONTENT_SETTING_BLOCK;
return CONTENT_SETTING_DEFAULT;
}
void NotificationProvider::ResetAllowedOrigin(const GURL& origin) {
if (profile_->IsOffTheRecord())
return;
// Since this isn't called often, let the normal observer behavior update the
// cache in this case.
PrefService* prefs = profile_->GetPrefs();
{
ListPrefUpdate update(prefs, prefs::kDesktopNotificationAllowedOrigins);
ListValue* allowed_sites = update.Get();
StringValue value(origin.spec());
int removed_index = allowed_sites->Remove(value);
DCHECK_NE(-1, removed_index) << origin << " was not allowed";
}
prefs->ScheduleSavePersistentPrefs();
}
void NotificationProvider::ResetBlockedOrigin(const GURL& origin) {
if (profile_->IsOffTheRecord())
return;
// Since this isn't called often, let the normal observer behavior update the
// cache in this case.
PrefService* prefs = profile_->GetPrefs();
{
ListPrefUpdate update(prefs, prefs::kDesktopNotificationDeniedOrigins);
ListValue* denied_sites = update.Get();
StringValue value(origin.spec());
int removed_index = denied_sites->Remove(value);
DCHECK_NE(-1, removed_index) << origin << " was not blocked";
}
prefs->ScheduleSavePersistentPrefs();
}
void NotificationProvider::ResetAllOrigins() {
PrefService* prefs = profile_->GetPrefs();
prefs->ClearPref(prefs::kDesktopNotificationAllowedOrigins);
prefs->ClearPref(prefs::kDesktopNotificationDeniedOrigins);
}
} // namespace content_settings