blob: d27e666a62df295f0e21f257eca9ba50089eb1fa [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 <algorithm>
#include "base/basictypes.h"
#include "base/compiler_specific.h"
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/metrics/histogram.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/protector/base_setting_change.h"
#include "chrome/browser/protector/histograms.h"
#include "chrome/browser/protector/protector_service.h"
#include "chrome/browser/protector/protector_service_factory.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/browser/search_engines/template_url_prepopulate_data.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "chrome/browser/search_engines/template_url_service_observer.h"
#include "chrome/browser/webdata/keyword_table.h"
#include "chrome/common/chrome_notification_types.h"
#include "chrome/common/url_constants.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h"
#include "content/public/browser/notification_service.h"
#include "googleurl/src/gurl.h"
#include "grit/chromium_strings.h"
#include "grit/generated_resources.h"
#include "grit/theme_resources.h"
#include "ui/base/l10n/l10n_util.h"
namespace protector {
namespace {
// Maximum length of the search engine name to be displayed.
const size_t kMaxDisplayedNameLength = 16;
// Matches TemplateURL with all fields set from the prepopulated data equal
// to fields in another TemplateURL.
class TemplateURLIsSame {
public:
// Creates a matcher based on |other|.
explicit TemplateURLIsSame(const TemplateURL* other) : other_(other) {
}
// Returns true if both |other| and |url| are NULL or have same field values.
bool operator()(const TemplateURL* url) {
if (url == other_ )
return true;
if (!url || !other_)
return false;
return url->short_name() == other_->short_name() &&
url->HasSameKeywordAs(*other_) &&
url->url() == other_->url() &&
url->suggestions_url() == other_->suggestions_url() &&
url->instant_url() == other_->instant_url() &&
url->safe_for_autoreplace() == other_->safe_for_autoreplace() &&
url->favicon_url() == other_->favicon_url() &&
url->show_in_default_list() == other_->show_in_default_list() &&
url->input_encodings() == other_->input_encodings() &&
url->prepopulate_id() == other_->prepopulate_id();
}
private:
const TemplateURL* other_;
};
} // namespace
// Default search engine change tracked by Protector.
class DefaultSearchProviderChange : public BaseSettingChange,
public TemplateURLServiceObserver,
public content::NotificationObserver {
public:
DefaultSearchProviderChange(TemplateURL* new_search_provider,
TemplateURL* backup_search_provider);
// BaseSettingChange overrides:
virtual bool Init(Profile* profile) OVERRIDE;
virtual void InitWhenDisabled(Profile* profile) OVERRIDE;
virtual void Apply(Browser* browser) OVERRIDE;
virtual void Discard(Browser* browser) OVERRIDE;
virtual void Timeout() OVERRIDE;
virtual int GetBadgeIconID() const OVERRIDE;
virtual int GetMenuItemIconID() const OVERRIDE;
virtual int GetBubbleIconID() const OVERRIDE;
virtual string16 GetBubbleTitle() const OVERRIDE;
virtual string16 GetBubbleMessage() const OVERRIDE;
virtual string16 GetApplyButtonText() const OVERRIDE;
virtual string16 GetDiscardButtonText() const OVERRIDE;
virtual DisplayName GetApplyDisplayName() const OVERRIDE;
virtual GURL GetNewSettingURL() const OVERRIDE;
private:
virtual ~DefaultSearchProviderChange();
// TemplateURLServiceObserver overrides:
virtual void OnTemplateURLServiceChanged() OVERRIDE;
// content::NotificationObserver overrides:
virtual void Observe(int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) OVERRIDE;
// Sets the default search provider to |*search_provider|. If a matching
// TemplateURL already exists, it is reused and |*search_provider| is reset.
// Otherwise, adds |*search_provider| to keywords and releases it.
// In both cases, |*search_provider| is NULL after this call.
// Returns the new default search provider (either |*search_provider| or the
// reused TemplateURL).
const TemplateURL* SetDefaultSearchProvider(
scoped_ptr<TemplateURL>* search_provider);
// Returns true if |new_search_provider_| can be used as the default search
// provider.
bool NewSearchProviderValid() const;
// Opens the Search engine settings page in a new tab.
void OpenSearchEngineSettings(Browser* browser);
// Returns the TemplateURLService instance for the Profile this change is
// related to.
TemplateURLService* GetTemplateURLService();
// Histogram ID of the new search provider.
int new_histogram_id_;
// Indicates that the default search was restored to the prepopulated default
// search engines.
bool is_fallback_;
// Indicates that the the prepopulated default search is the same as
// |new_search_provider_|.
bool fallback_is_new_;
// ID of |new_search_provider_|.
TemplateURLID new_id_;
// The default search at the moment the change was detected. Will be used to
// restore the new default search back if Apply is called. Will be set to
// |NULL| if removed from the TemplateURLService.
TemplateURL* new_search_provider_;
// Default search provider set by Init for the period until user makes a
// choice and either Apply or Discard is performed. Never is |NULL| during
// that period since the change will dismiss itself if this provider gets
// deleted or stops being the default.
const TemplateURL* default_search_provider_;
// Stores backup of the default search until it becomes owned by the
// TemplateURLService or deleted.
scoped_ptr<TemplateURL> backup_search_provider_;
content::NotificationRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(DefaultSearchProviderChange);
};
DefaultSearchProviderChange::DefaultSearchProviderChange(
TemplateURL* new_search_provider,
TemplateURL* backup_search_provider)
: new_histogram_id_(GetSearchProviderHistogramID(new_search_provider)),
is_fallback_(false),
fallback_is_new_(false),
new_search_provider_(new_search_provider),
default_search_provider_(NULL),
backup_search_provider_(backup_search_provider) {
if (backup_search_provider) {
UMA_HISTOGRAM_ENUMERATION(
kProtectorHistogramSearchProviderHijacked,
new_histogram_id_,
kProtectorMaxSearchProviderID);
} else {
UMA_HISTOGRAM_ENUMERATION(
kProtectorHistogramSearchProviderCorrupt,
new_histogram_id_,
kProtectorMaxSearchProviderID);
}
}
DefaultSearchProviderChange::~DefaultSearchProviderChange() {
if (profile())
GetTemplateURLService()->RemoveObserver(this);
}
bool DefaultSearchProviderChange::Init(Profile* profile) {
if (!BaseSettingChange::Init(profile))
return false;
if (!backup_search_provider_.get() ||
!backup_search_provider_->SupportsReplacement()) {
// Fallback to a prepopulated default search provider, ignoring any
// overrides in Prefs.
backup_search_provider_.reset(
TemplateURLPrepopulateData::GetPrepopulatedDefaultSearch(NULL));
is_fallback_ = true;
VLOG(1) << "Fallback search provider: "
<< backup_search_provider_->short_name();
}
default_search_provider_ = SetDefaultSearchProvider(&backup_search_provider_);
DCHECK(default_search_provider_);
// |backup_search_provider_| should be |NULL| since now.
DCHECK(!backup_search_provider_.get());
if (is_fallback_ && default_search_provider_ == new_search_provider_)
fallback_is_new_ = true;
int restored_histogram_id =
GetSearchProviderHistogramID(default_search_provider_);
UMA_HISTOGRAM_ENUMERATION(
kProtectorHistogramSearchProviderRestored,
restored_histogram_id,
kProtectorMaxSearchProviderID);
if (is_fallback_) {
VLOG(1) << "Fallback to search provider: "
<< default_search_provider_->short_name();
UMA_HISTOGRAM_ENUMERATION(
kProtectorHistogramSearchProviderFallback,
restored_histogram_id,
kProtectorMaxSearchProviderID);
}
// Listen for the default search provider changes.
GetTemplateURLService()->AddObserver(this);
if (new_search_provider_) {
// Listen for removal of |new_search_provider_|.
new_id_ = new_search_provider_->id();
registrar_.Add(
this, chrome::NOTIFICATION_TEMPLATE_URL_REMOVED,
content::Source<Profile>(profile->GetOriginalProfile()));
}
return true;
}
void DefaultSearchProviderChange::InitWhenDisabled(Profile* profile) {
// The --no-protector case is handled in TemplateURLService internals.
// TODO(ivankr): move it here.
NOTREACHED();
}
void DefaultSearchProviderChange::Apply(Browser* browser) {
UMA_HISTOGRAM_ENUMERATION(
kProtectorHistogramSearchProviderApplied,
new_histogram_id_,
kProtectorMaxSearchProviderID);
GetTemplateURLService()->RemoveObserver(this);
if (NewSearchProviderValid()) {
GetTemplateURLService()->SetDefaultSearchProvider(new_search_provider_);
} else {
// Open settings page in case the new setting is invalid.
OpenSearchEngineSettings(browser);
}
}
void DefaultSearchProviderChange::Discard(Browser* browser) {
UMA_HISTOGRAM_ENUMERATION(
kProtectorHistogramSearchProviderDiscarded,
new_histogram_id_,
kProtectorMaxSearchProviderID);
GetTemplateURLService()->RemoveObserver(this);
if (is_fallback_) {
// Open settings page in case the old setting is invalid.
OpenSearchEngineSettings(browser);
}
// Nothing to do otherwise since we have already set the search engine
// to |old_id_| in |Init|.
}
void DefaultSearchProviderChange::Timeout() {
UMA_HISTOGRAM_ENUMERATION(
kProtectorHistogramSearchProviderTimeout,
new_histogram_id_,
kProtectorMaxSearchProviderID);
}
int DefaultSearchProviderChange::GetBadgeIconID() const {
return IDR_SEARCH_ENGINE_CHANGE_BADGE;
}
int DefaultSearchProviderChange::GetMenuItemIconID() const {
return IDR_SEARCH_ENGINE_CHANGE_MENU;
}
int DefaultSearchProviderChange::GetBubbleIconID() const {
return IDR_SEARCH_ENGINE_CHANGE_ALERT;
}
string16 DefaultSearchProviderChange::GetBubbleTitle() const {
return l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_CHANGE_TITLE);
}
string16 DefaultSearchProviderChange::GetBubbleMessage() const {
if (is_fallback_) {
return l10n_util::GetStringFUTF16(
IDS_SEARCH_ENGINE_CHANGE_NO_BACKUP_MESSAGE,
default_search_provider_->short_name());
}
return l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_CHANGE_MESSAGE);
}
string16 DefaultSearchProviderChange::GetApplyButtonText() const {
if (NewSearchProviderValid()) {
// If backup search engine is lost and fallback was made to the current
// search provider then there is no need to show this button.
if (fallback_is_new_)
return string16();
string16 name = new_search_provider_->short_name();
return (name.length() > kMaxDisplayedNameLength) ?
l10n_util::GetStringUTF16(IDS_CHANGE_SEARCH_ENGINE_NO_NAME) :
l10n_util::GetStringFUTF16(IDS_CHANGE_SEARCH_ENGINE, name);
}
if (is_fallback_) {
// Both settings are lost: don't show this button.
return string16();
}
// New setting is lost, offer to go to settings.
return l10n_util::GetStringUTF16(IDS_SELECT_SEARCH_ENGINE);
}
string16 DefaultSearchProviderChange::GetDiscardButtonText() const {
if (!is_fallback_) {
string16 name = default_search_provider_->short_name();
return (name.length() > kMaxDisplayedNameLength) ?
l10n_util::GetStringUTF16(IDS_KEEP_SETTING) :
l10n_util::GetStringFUTF16(IDS_KEEP_SEARCH_ENGINE, name);
}
// Old setting is lost, offer to go to settings.
return l10n_util::GetStringUTF16(IDS_SELECT_SEARCH_ENGINE);
}
BaseSettingChange::DisplayName
DefaultSearchProviderChange::GetApplyDisplayName() const {
if (NewSearchProviderValid() &&
new_search_provider_->short_name().length() <= kMaxDisplayedNameLength) {
return DisplayName(kDefaultSearchProviderChangeNamePriority,
new_search_provider_->short_name());
}
// Return the default empty name.
return BaseSettingChange::GetApplyDisplayName();
}
GURL DefaultSearchProviderChange::GetNewSettingURL() const {
if (is_fallback_) {
// Do not collide this change when fallback was made, since the message
// and Discard behaviour is pretty different.
return GURL();
}
if (!NewSearchProviderValid())
return GURL();
return TemplateURLService::GenerateSearchURL(new_search_provider_);
}
void DefaultSearchProviderChange::OnTemplateURLServiceChanged() {
TemplateURLService* url_service = GetTemplateURLService();
if (url_service->GetDefaultSearchProvider() != default_search_provider_) {
VLOG(1) << "Default search provider has been changed by user";
default_search_provider_ = NULL;
url_service->RemoveObserver(this);
// Will delete this DefaultSearchProviderChange instance.
ProtectorServiceFactory::GetForProfile(profile())->DismissChange(this);
}
}
void DefaultSearchProviderChange::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
DCHECK(type == chrome::NOTIFICATION_TEMPLATE_URL_REMOVED);
TemplateURLID id = *content::Details<TemplateURLID>(details).ptr();
if (id == new_id_)
new_search_provider_ = NULL;
registrar_.Remove(this, type, source);
// TODO(ivankr): should the change be dismissed as well? Probably not,
// since this may happend due to Sync or whatever. In that case, if user
// clicks on 'Change to...', the Search engine settings page will be opened.
}
const TemplateURL* DefaultSearchProviderChange::SetDefaultSearchProvider(
scoped_ptr<TemplateURL>* search_provider) {
TemplateURLService* url_service = GetTemplateURLService();
TemplateURLService::TemplateURLVector urls = url_service->GetTemplateURLs();
TemplateURL* new_default_provider = NULL;
// Check if this provider already exists and add it otherwise.
TemplateURLService::TemplateURLVector::const_iterator i =
std::find_if(urls.begin(), urls.end(),
TemplateURLIsSame(search_provider->get()));
if (i != urls.end()) {
VLOG(1) << "Provider already exists";
new_default_provider = *i;
search_provider->reset();
} else {
VLOG(1) << "No match, adding new provider";
new_default_provider = search_provider->get();
url_service->Add(search_provider->release());
UMA_HISTOGRAM_ENUMERATION(
kProtectorHistogramSearchProviderMissing,
GetSearchProviderHistogramID(new_default_provider),
kProtectorMaxSearchProviderID);
}
// TODO(ivankr): handle keyword conflicts with existing providers.
DCHECK(new_default_provider);
VLOG(1) << "Default search provider set to: "
<< new_default_provider->short_name();
url_service->SetDefaultSearchProvider(new_default_provider);
return new_default_provider;
}
bool DefaultSearchProviderChange::NewSearchProviderValid() const {
return new_search_provider_ && new_search_provider_->SupportsReplacement();
}
void DefaultSearchProviderChange::OpenSearchEngineSettings(Browser* browser) {
ProtectorServiceFactory::GetForProfile(profile())->OpenTab(
GURL(std::string(chrome::kChromeUISettingsURL) +
chrome::kSearchEnginesSubPage), browser);
}
TemplateURLService* DefaultSearchProviderChange::GetTemplateURLService() {
TemplateURLService* url_service =
TemplateURLServiceFactory::GetForProfile(profile());
DCHECK(url_service);
return url_service;
}
BaseSettingChange* CreateDefaultSearchProviderChange(TemplateURL* actual,
TemplateURL* backup) {
return new DefaultSearchProviderChange(actual, backup);
}
} // namespace protector