// 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/google/google_url_tracker.h"
#include <vector>
#include "base/command_line.h"
#include "base/compiler_specific.h"
#include "base/string_util.h"
#include "base/utf_string_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/prefs/pref_service.h"
#include "chrome/browser/search_engines/template_url.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "content/browser/tab_contents/navigation_controller.h"
#include "content/browser/tab_contents/tab_contents.h"
#include "content/common/notification_service.h"
#include "grit/generated_resources.h"
#include "net/base/load_flags.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
#include "ui/base/l10n/l10n_util.h"
namespace {
InfoBarDelegate* CreateInfobar(TabContents* tab_contents,
GoogleURLTracker* google_url_tracker,
const GURL& new_google_url) {
InfoBarDelegate* infobar = new GoogleURLTrackerInfoBarDelegate(tab_contents,
google_url_tracker, new_google_url);
tab_contents->AddInfoBar(infobar);
return infobar;
}
} // namespace
// GoogleURLTrackerInfoBarDelegate --------------------------------------------
GoogleURLTrackerInfoBarDelegate::GoogleURLTrackerInfoBarDelegate(
TabContents* tab_contents,
GoogleURLTracker* google_url_tracker,
const GURL& new_google_url)
: ConfirmInfoBarDelegate(tab_contents),
google_url_tracker_(google_url_tracker),
new_google_url_(new_google_url) {
}
bool GoogleURLTrackerInfoBarDelegate::Accept() {
google_url_tracker_->AcceptGoogleURL(new_google_url_);
google_url_tracker_->RedoSearch();
return true;
}
bool GoogleURLTrackerInfoBarDelegate::Cancel() {
google_url_tracker_->CancelGoogleURL(new_google_url_);
return true;
}
void GoogleURLTrackerInfoBarDelegate::InfoBarClosed() {
google_url_tracker_->InfoBarClosed();
delete this;
}
GoogleURLTrackerInfoBarDelegate::~GoogleURLTrackerInfoBarDelegate() {
}
string16 GoogleURLTrackerInfoBarDelegate::GetMessageText() const {
// TODO(ukai): change new_google_url to google_base_domain?
return l10n_util::GetStringFUTF16(IDS_GOOGLE_URL_TRACKER_INFOBAR_MESSAGE,
UTF8ToUTF16(new_google_url_.spec()));
}
string16 GoogleURLTrackerInfoBarDelegate::GetButtonLabel(
InfoBarButton button) const {
return l10n_util::GetStringUTF16((button == BUTTON_OK) ?
IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL :
IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL);
}
// GoogleURLTracker -----------------------------------------------------------
const char GoogleURLTracker::kDefaultGoogleHomepage[] =
"http://www.google.com/";
const char GoogleURLTracker::kSearchDomainCheckURL[] =
"https://www.google.com/searchdomaincheck?format=domain&type=chrome";
GoogleURLTracker::GoogleURLTracker()
: infobar_creator_(&CreateInfobar),
google_url_(g_browser_process->local_state()->GetString(
prefs::kLastKnownGoogleURL)),
ALLOW_THIS_IN_INITIALIZER_LIST(runnable_method_factory_(this)),
fetcher_id_(0),
queue_wakeup_task_(true),
in_startup_sleep_(true),
already_fetched_(false),
need_to_fetch_(false),
need_to_prompt_(false),
controller_(NULL),
infobar_(NULL) {
net::NetworkChangeNotifier::AddIPAddressObserver(this);
MessageLoop::current()->PostTask(FROM_HERE,
runnable_method_factory_.NewRunnableMethod(
&GoogleURLTracker::QueueWakeupTask));
}
GoogleURLTracker::~GoogleURLTracker() {
runnable_method_factory_.RevokeAll();
net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
}
// static
GURL GoogleURLTracker::GoogleURL() {
const GoogleURLTracker* const tracker =
g_browser_process->google_url_tracker();
return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage);
}
// static
void GoogleURLTracker::RequestServerCheck() {
GoogleURLTracker* const tracker = g_browser_process->google_url_tracker();
if (tracker)
tracker->SetNeedToFetch();
}
// static
void GoogleURLTracker::RegisterPrefs(PrefService* prefs) {
prefs->RegisterStringPref(prefs::kLastKnownGoogleURL,
kDefaultGoogleHomepage);
prefs->RegisterStringPref(prefs::kLastPromptedGoogleURL, std::string());
}
// static
void GoogleURLTracker::GoogleURLSearchCommitted() {
GoogleURLTracker* tracker = g_browser_process->google_url_tracker();
if (tracker)
tracker->SearchCommitted();
}
void GoogleURLTracker::SetNeedToFetch() {
need_to_fetch_ = true;
StartFetchIfDesirable();
}
void GoogleURLTracker::QueueWakeupTask() {
// When testing, we want to wake from sleep at controlled times, not on a
// timer.
if (!queue_wakeup_task_)
return;
// Because this function can be called during startup, when kicking off a URL
// fetch can eat up 20 ms of time, we delay five seconds, which is hopefully
// long enough to be after startup, but still get results back quickly.
// Ideally, instead of this timer, we'd do something like "check if the
// browser is starting up, and if so, come back later", but there is currently
// no function to do this.
static const int kStartFetchDelayMS = 5000;
MessageLoop::current()->PostDelayedTask(FROM_HERE,
runnable_method_factory_.NewRunnableMethod(
&GoogleURLTracker::FinishSleep),
kStartFetchDelayMS);
}
void GoogleURLTracker::FinishSleep() {
in_startup_sleep_ = false;
StartFetchIfDesirable();
}
void GoogleURLTracker::StartFetchIfDesirable() {
// Bail if a fetch isn't appropriate right now. This function will be called
// again each time one of the preconditions changes, so we'll fetch
// immediately once all of them are met.
//
// See comments in header on the class, on RequestServerCheck(), and on the
// various members here for more detail on exactly what the conditions are.
if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_)
return;
if (CommandLine::ForCurrentProcess()->HasSwitch(
switches::kDisableBackgroundNetworking))
return;
already_fetched_ = true;
fetcher_.reset(URLFetcher::Create(fetcher_id_, GURL(kSearchDomainCheckURL),
URLFetcher::GET, this));
++fetcher_id_;
// We don't want this fetch to affect existing state in local_state. For
// example, if a user has no Google cookies, this automatic check should not
// cause one to be set, lest we alarm the user.
fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE |
net::LOAD_DO_NOT_SAVE_COOKIES);
fetcher_->set_request_context(g_browser_process->system_request_context());
// Configure to max_retries at most kMaxRetries times for 5xx errors.
static const int kMaxRetries = 5;
fetcher_->set_max_retries(kMaxRetries);
fetcher_->Start();
}
void GoogleURLTracker::OnURLFetchComplete(const URLFetcher* source,
const GURL& url,
const net::URLRequestStatus& status,
int response_code,
const ResponseCookies& cookies,
const std::string& data) {
// Delete the fetcher on this function's exit.
scoped_ptr<URLFetcher> clean_up_fetcher(fetcher_.release());
// Don't update the URL if the request didn't succeed.
if (!status.is_success() || (response_code != 200)) {
already_fetched_ = false;
return;
}
// See if the response data was one we want to use, and if so, convert to the
// appropriate Google base URL.
std::string url_str;
TrimWhitespace(data, TRIM_ALL, &url_str);
if (!StartsWithASCII(url_str, ".google.", false))
return;
fetched_google_url_ = GURL("http://www" + url_str);
GURL last_prompted_url(
g_browser_process->local_state()->GetString(
prefs::kLastPromptedGoogleURL));
need_to_prompt_ = false;
if (last_prompted_url.is_empty()) {
// On the very first run of Chrome, when we've never looked up the URL at
// all, we should just silently switch over to whatever we get immediately.
AcceptGoogleURL(fetched_google_url_);
return;
}
// If the URL hasn't changed, then whether |need_to_prompt_| is true or false,
// nothing has changed, so just bail.
if (fetched_google_url_ == last_prompted_url)
return;
if (fetched_google_url_ == google_url_) {
// The user came back to their original location after having temporarily
// moved. Reset the prompted URL so we'll prompt again if they move again.
CancelGoogleURL(fetched_google_url_);
return;
}
need_to_prompt_ = true;
}
void GoogleURLTracker::AcceptGoogleURL(const GURL& new_google_url) {
google_url_ = new_google_url;
g_browser_process->local_state()->SetString(prefs::kLastKnownGoogleURL,
google_url_.spec());
g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL,
google_url_.spec());
NotificationService::current()->Notify(NotificationType::GOOGLE_URL_UPDATED,
NotificationService::AllSources(),
NotificationService::NoDetails());
need_to_prompt_ = false;
}
void GoogleURLTracker::CancelGoogleURL(const GURL& new_google_url) {
g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL,
new_google_url.spec());
need_to_prompt_ = false;
}
void GoogleURLTracker::InfoBarClosed() {
registrar_.RemoveAll();
controller_ = NULL;
infobar_ = NULL;
search_url_ = GURL();
}
void GoogleURLTracker::RedoSearch() {
// Re-do the user's search on the new domain.
DCHECK(controller_);
url_canon::Replacements<char> replacements;
replacements.SetHost(google_url_.host().data(),
url_parse::Component(0, google_url_.host().length()));
GURL new_search_url(search_url_.ReplaceComponents(replacements));
if (new_search_url.is_valid())
controller_->tab_contents()->OpenURL(new_search_url, GURL(), CURRENT_TAB,
PageTransition::GENERATED);
}
void GoogleURLTracker::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
switch (type.value) {
case NotificationType::NAV_ENTRY_PENDING: {
NavigationController* controller =
Source<NavigationController>(source).ptr();
OnNavigationPending(source, controller->pending_entry()->url());
break;
}
case NotificationType::NAV_ENTRY_COMMITTED:
case NotificationType::TAB_CLOSED:
OnNavigationCommittedOrTabClosed(
Source<NavigationController>(source).ptr()->tab_contents(),
type.value);
break;
default:
NOTREACHED() << "Unknown notification received:" << type.value;
}
}
void GoogleURLTracker::OnIPAddressChanged() {
already_fetched_ = false;
StartFetchIfDesirable();
}
void GoogleURLTracker::SearchCommitted() {
if (registrar_.IsEmpty() && (need_to_prompt_ || fetcher_.get())) {
// This notification will fire a bit later in the same call chain we're
// currently in.
registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING,
NotificationService::AllSources());
}
}
void GoogleURLTracker::OnNavigationPending(const NotificationSource& source,
const GURL& pending_url) {
controller_ = Source<NavigationController>(source).ptr();
search_url_ = pending_url;
registrar_.Remove(this, NotificationType::NAV_ENTRY_PENDING,
NotificationService::AllSources());
// Start listening for the commit notification. We also need to listen for the
// tab close command since that means the load will never commit.
registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
Source<NavigationController>(controller_));
registrar_.Add(this, NotificationType::TAB_CLOSED,
Source<NavigationController>(controller_));
}
void GoogleURLTracker::OnNavigationCommittedOrTabClosed(
TabContents* tab_contents,
NotificationType::Type type) {
registrar_.RemoveAll();
if (type == NotificationType::NAV_ENTRY_COMMITTED) {
ShowGoogleURLInfoBarIfNecessary(tab_contents);
} else {
controller_ = NULL;
infobar_ = NULL;
}
}
void GoogleURLTracker::ShowGoogleURLInfoBarIfNecessary(
TabContents* tab_contents) {
if (!need_to_prompt_)
return;
DCHECK(!fetched_google_url_.is_empty());
infobar_ = (*infobar_creator_)(tab_contents, this, fetched_google_url_);
}