// 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_); }