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