Home | History | Annotate | Download | only in google
      1 // Copyright (c) 2011 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/google/google_url_tracker.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/command_line.h"
     10 #include "base/compiler_specific.h"
     11 #include "base/string_util.h"
     12 #include "base/utf_string_conversions.h"
     13 #include "chrome/browser/browser_process.h"
     14 #include "chrome/browser/prefs/pref_service.h"
     15 #include "chrome/browser/search_engines/template_url.h"
     16 #include "chrome/common/chrome_switches.h"
     17 #include "chrome/common/pref_names.h"
     18 #include "content/browser/tab_contents/navigation_controller.h"
     19 #include "content/browser/tab_contents/tab_contents.h"
     20 #include "content/common/notification_service.h"
     21 #include "grit/generated_resources.h"
     22 #include "net/base/load_flags.h"
     23 #include "net/url_request/url_request_context_getter.h"
     24 #include "net/url_request/url_request_status.h"
     25 #include "ui/base/l10n/l10n_util.h"
     26 
     27 namespace {
     28 
     29 InfoBarDelegate* CreateInfobar(TabContents* tab_contents,
     30                                GoogleURLTracker* google_url_tracker,
     31                                const GURL& new_google_url) {
     32   InfoBarDelegate* infobar = new GoogleURLTrackerInfoBarDelegate(tab_contents,
     33       google_url_tracker, new_google_url);
     34   tab_contents->AddInfoBar(infobar);
     35   return infobar;
     36 }
     37 
     38 }  // namespace
     39 
     40 // GoogleURLTrackerInfoBarDelegate --------------------------------------------
     41 
     42 GoogleURLTrackerInfoBarDelegate::GoogleURLTrackerInfoBarDelegate(
     43     TabContents* tab_contents,
     44     GoogleURLTracker* google_url_tracker,
     45     const GURL& new_google_url)
     46     : ConfirmInfoBarDelegate(tab_contents),
     47       google_url_tracker_(google_url_tracker),
     48       new_google_url_(new_google_url) {
     49 }
     50 
     51 bool GoogleURLTrackerInfoBarDelegate::Accept() {
     52   google_url_tracker_->AcceptGoogleURL(new_google_url_);
     53   google_url_tracker_->RedoSearch();
     54   return true;
     55 }
     56 
     57 bool GoogleURLTrackerInfoBarDelegate::Cancel() {
     58   google_url_tracker_->CancelGoogleURL(new_google_url_);
     59   return true;
     60 }
     61 
     62 void GoogleURLTrackerInfoBarDelegate::InfoBarClosed() {
     63   google_url_tracker_->InfoBarClosed();
     64   delete this;
     65 }
     66 
     67 GoogleURLTrackerInfoBarDelegate::~GoogleURLTrackerInfoBarDelegate() {
     68 }
     69 
     70 string16 GoogleURLTrackerInfoBarDelegate::GetMessageText() const {
     71   // TODO(ukai): change new_google_url to google_base_domain?
     72   return l10n_util::GetStringFUTF16(IDS_GOOGLE_URL_TRACKER_INFOBAR_MESSAGE,
     73                                     UTF8ToUTF16(new_google_url_.spec()));
     74 }
     75 
     76 string16 GoogleURLTrackerInfoBarDelegate::GetButtonLabel(
     77     InfoBarButton button) const {
     78   return l10n_util::GetStringUTF16((button == BUTTON_OK) ?
     79       IDS_CONFIRM_MESSAGEBOX_YES_BUTTON_LABEL :
     80       IDS_CONFIRM_MESSAGEBOX_NO_BUTTON_LABEL);
     81 }
     82 
     83 
     84 // GoogleURLTracker -----------------------------------------------------------
     85 
     86 const char GoogleURLTracker::kDefaultGoogleHomepage[] =
     87     "http://www.google.com/";
     88 const char GoogleURLTracker::kSearchDomainCheckURL[] =
     89     "https://www.google.com/searchdomaincheck?format=domain&type=chrome";
     90 
     91 GoogleURLTracker::GoogleURLTracker()
     92     : infobar_creator_(&CreateInfobar),
     93       google_url_(g_browser_process->local_state()->GetString(
     94           prefs::kLastKnownGoogleURL)),
     95       ALLOW_THIS_IN_INITIALIZER_LIST(runnable_method_factory_(this)),
     96       fetcher_id_(0),
     97       queue_wakeup_task_(true),
     98       in_startup_sleep_(true),
     99       already_fetched_(false),
    100       need_to_fetch_(false),
    101       need_to_prompt_(false),
    102       controller_(NULL),
    103       infobar_(NULL) {
    104   net::NetworkChangeNotifier::AddIPAddressObserver(this);
    105 
    106   MessageLoop::current()->PostTask(FROM_HERE,
    107                                    runnable_method_factory_.NewRunnableMethod(
    108                                    &GoogleURLTracker::QueueWakeupTask));
    109 }
    110 
    111 GoogleURLTracker::~GoogleURLTracker() {
    112   runnable_method_factory_.RevokeAll();
    113   net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
    114 }
    115 
    116 // static
    117 GURL GoogleURLTracker::GoogleURL() {
    118   const GoogleURLTracker* const tracker =
    119       g_browser_process->google_url_tracker();
    120   return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage);
    121 }
    122 
    123 // static
    124 void GoogleURLTracker::RequestServerCheck() {
    125   GoogleURLTracker* const tracker = g_browser_process->google_url_tracker();
    126   if (tracker)
    127     tracker->SetNeedToFetch();
    128 }
    129 
    130 // static
    131 void GoogleURLTracker::RegisterPrefs(PrefService* prefs) {
    132   prefs->RegisterStringPref(prefs::kLastKnownGoogleURL,
    133                             kDefaultGoogleHomepage);
    134   prefs->RegisterStringPref(prefs::kLastPromptedGoogleURL, std::string());
    135 }
    136 
    137 // static
    138 void GoogleURLTracker::GoogleURLSearchCommitted() {
    139   GoogleURLTracker* tracker = g_browser_process->google_url_tracker();
    140   if (tracker)
    141     tracker->SearchCommitted();
    142 }
    143 
    144 void GoogleURLTracker::SetNeedToFetch() {
    145   need_to_fetch_ = true;
    146   StartFetchIfDesirable();
    147 }
    148 
    149 void GoogleURLTracker::QueueWakeupTask() {
    150   // When testing, we want to wake from sleep at controlled times, not on a
    151   // timer.
    152   if (!queue_wakeup_task_)
    153     return;
    154 
    155   // Because this function can be called during startup, when kicking off a URL
    156   // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully
    157   // long enough to be after startup, but still get results back quickly.
    158   // Ideally, instead of this timer, we'd do something like "check if the
    159   // browser is starting up, and if so, come back later", but there is currently
    160   // no function to do this.
    161   static const int kStartFetchDelayMS = 5000;
    162   MessageLoop::current()->PostDelayedTask(FROM_HERE,
    163       runnable_method_factory_.NewRunnableMethod(
    164           &GoogleURLTracker::FinishSleep),
    165       kStartFetchDelayMS);
    166 }
    167 
    168 void GoogleURLTracker::FinishSleep() {
    169   in_startup_sleep_ = false;
    170   StartFetchIfDesirable();
    171 }
    172 
    173 void GoogleURLTracker::StartFetchIfDesirable() {
    174   // Bail if a fetch isn't appropriate right now.  This function will be called
    175   // again each time one of the preconditions changes, so we'll fetch
    176   // immediately once all of them are met.
    177   //
    178   // See comments in header on the class, on RequestServerCheck(), and on the
    179   // various members here for more detail on exactly what the conditions are.
    180   if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_)
    181     return;
    182 
    183   if (CommandLine::ForCurrentProcess()->HasSwitch(
    184       switches::kDisableBackgroundNetworking))
    185     return;
    186 
    187   already_fetched_ = true;
    188   fetcher_.reset(URLFetcher::Create(fetcher_id_, GURL(kSearchDomainCheckURL),
    189                                     URLFetcher::GET, this));
    190   ++fetcher_id_;
    191   // We don't want this fetch to affect existing state in local_state.  For
    192   // example, if a user has no Google cookies, this automatic check should not
    193   // cause one to be set, lest we alarm the user.
    194   fetcher_->set_load_flags(net::LOAD_DISABLE_CACHE |
    195                            net::LOAD_DO_NOT_SAVE_COOKIES);
    196   fetcher_->set_request_context(g_browser_process->system_request_context());
    197 
    198   // Configure to max_retries at most kMaxRetries times for 5xx errors.
    199   static const int kMaxRetries = 5;
    200   fetcher_->set_max_retries(kMaxRetries);
    201 
    202   fetcher_->Start();
    203 }
    204 
    205 void GoogleURLTracker::OnURLFetchComplete(const URLFetcher* source,
    206                                           const GURL& url,
    207                                           const net::URLRequestStatus& status,
    208                                           int response_code,
    209                                           const ResponseCookies& cookies,
    210                                           const std::string& data) {
    211   // Delete the fetcher on this function's exit.
    212   scoped_ptr<URLFetcher> clean_up_fetcher(fetcher_.release());
    213 
    214   // Don't update the URL if the request didn't succeed.
    215   if (!status.is_success() || (response_code != 200)) {
    216     already_fetched_ = false;
    217     return;
    218   }
    219 
    220   // See if the response data was one we want to use, and if so, convert to the
    221   // appropriate Google base URL.
    222   std::string url_str;
    223   TrimWhitespace(data, TRIM_ALL, &url_str);
    224 
    225   if (!StartsWithASCII(url_str, ".google.", false))
    226     return;
    227 
    228   fetched_google_url_ = GURL("http://www" + url_str);
    229   GURL last_prompted_url(
    230       g_browser_process->local_state()->GetString(
    231           prefs::kLastPromptedGoogleURL));
    232   need_to_prompt_ = false;
    233 
    234   if (last_prompted_url.is_empty()) {
    235     // On the very first run of Chrome, when we've never looked up the URL at
    236     // all, we should just silently switch over to whatever we get immediately.
    237     AcceptGoogleURL(fetched_google_url_);
    238     return;
    239   }
    240 
    241   // If the URL hasn't changed, then whether |need_to_prompt_| is true or false,
    242   // nothing has changed, so just bail.
    243   if (fetched_google_url_ == last_prompted_url)
    244     return;
    245 
    246   if (fetched_google_url_ == google_url_) {
    247     // The user came back to their original location after having temporarily
    248     // moved.  Reset the prompted URL so we'll prompt again if they move again.
    249     CancelGoogleURL(fetched_google_url_);
    250     return;
    251   }
    252 
    253   need_to_prompt_ = true;
    254 }
    255 
    256 void GoogleURLTracker::AcceptGoogleURL(const GURL& new_google_url) {
    257   google_url_ = new_google_url;
    258   g_browser_process->local_state()->SetString(prefs::kLastKnownGoogleURL,
    259                                               google_url_.spec());
    260   g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL,
    261                                               google_url_.spec());
    262   NotificationService::current()->Notify(NotificationType::GOOGLE_URL_UPDATED,
    263                                          NotificationService::AllSources(),
    264                                          NotificationService::NoDetails());
    265   need_to_prompt_ = false;
    266 }
    267 
    268 void GoogleURLTracker::CancelGoogleURL(const GURL& new_google_url) {
    269   g_browser_process->local_state()->SetString(prefs::kLastPromptedGoogleURL,
    270                                               new_google_url.spec());
    271   need_to_prompt_ = false;
    272 }
    273 
    274 void GoogleURLTracker::InfoBarClosed() {
    275   registrar_.RemoveAll();
    276   controller_ = NULL;
    277   infobar_ = NULL;
    278   search_url_ = GURL();
    279 }
    280 
    281 void GoogleURLTracker::RedoSearch() {
    282   //  Re-do the user's search on the new domain.
    283   DCHECK(controller_);
    284   url_canon::Replacements<char> replacements;
    285   replacements.SetHost(google_url_.host().data(),
    286                        url_parse::Component(0, google_url_.host().length()));
    287   GURL new_search_url(search_url_.ReplaceComponents(replacements));
    288   if (new_search_url.is_valid())
    289     controller_->tab_contents()->OpenURL(new_search_url, GURL(), CURRENT_TAB,
    290                                          PageTransition::GENERATED);
    291 }
    292 
    293 void GoogleURLTracker::Observe(NotificationType type,
    294                                const NotificationSource& source,
    295                                const NotificationDetails& details) {
    296   switch (type.value) {
    297     case NotificationType::NAV_ENTRY_PENDING: {
    298       NavigationController* controller =
    299           Source<NavigationController>(source).ptr();
    300       OnNavigationPending(source, controller->pending_entry()->url());
    301       break;
    302     }
    303 
    304     case NotificationType::NAV_ENTRY_COMMITTED:
    305     case NotificationType::TAB_CLOSED:
    306       OnNavigationCommittedOrTabClosed(
    307           Source<NavigationController>(source).ptr()->tab_contents(),
    308           type.value);
    309       break;
    310 
    311     default:
    312       NOTREACHED() << "Unknown notification received:" << type.value;
    313   }
    314 }
    315 
    316 void GoogleURLTracker::OnIPAddressChanged() {
    317   already_fetched_ = false;
    318   StartFetchIfDesirable();
    319 }
    320 
    321 void GoogleURLTracker::SearchCommitted() {
    322   if (registrar_.IsEmpty() && (need_to_prompt_ || fetcher_.get())) {
    323     // This notification will fire a bit later in the same call chain we're
    324     // currently in.
    325     registrar_.Add(this, NotificationType::NAV_ENTRY_PENDING,
    326                    NotificationService::AllSources());
    327   }
    328 }
    329 
    330 void GoogleURLTracker::OnNavigationPending(const NotificationSource& source,
    331                                            const GURL& pending_url) {
    332   controller_ = Source<NavigationController>(source).ptr();
    333   search_url_ = pending_url;
    334   registrar_.Remove(this, NotificationType::NAV_ENTRY_PENDING,
    335                     NotificationService::AllSources());
    336   // Start listening for the commit notification. We also need to listen for the
    337   // tab close command since that means the load will never commit.
    338   registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED,
    339                  Source<NavigationController>(controller_));
    340   registrar_.Add(this, NotificationType::TAB_CLOSED,
    341                  Source<NavigationController>(controller_));
    342 }
    343 
    344 void GoogleURLTracker::OnNavigationCommittedOrTabClosed(
    345     TabContents* tab_contents,
    346     NotificationType::Type type) {
    347   registrar_.RemoveAll();
    348 
    349   if (type == NotificationType::NAV_ENTRY_COMMITTED) {
    350     ShowGoogleURLInfoBarIfNecessary(tab_contents);
    351   } else {
    352     controller_ = NULL;
    353     infobar_ = NULL;
    354   }
    355 }
    356 
    357 void GoogleURLTracker::ShowGoogleURLInfoBarIfNecessary(
    358     TabContents* tab_contents) {
    359   if (!need_to_prompt_)
    360     return;
    361   DCHECK(!fetched_google_url_.is_empty());
    362 
    363   infobar_ = (*infobar_creator_)(tab_contents, this, fetched_google_url_);
    364 }
    365