Home | History | Annotate | Download | only in google
      1 // Copyright (c) 2012 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 "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/prefs/pref_service.h"
     10 #include "base/strings/string_util.h"
     11 #include "chrome/browser/chrome_notification_types.h"
     12 #include "chrome/browser/google/google_url_tracker_factory.h"
     13 #include "chrome/browser/google/google_url_tracker_infobar_delegate.h"
     14 #include "chrome/browser/google/google_url_tracker_navigation_helper.h"
     15 #include "chrome/browser/google/google_util.h"
     16 #include "chrome/browser/infobars/infobar.h"
     17 #include "chrome/browser/infobars/infobar_service.h"
     18 #include "chrome/browser/profiles/profile.h"
     19 #include "chrome/common/chrome_switches.h"
     20 #include "chrome/common/pref_names.h"
     21 #include "content/public/browser/navigation_controller.h"
     22 #include "content/public/browser/navigation_entry.h"
     23 #include "content/public/browser/notification_service.h"
     24 #include "net/base/load_flags.h"
     25 #include "net/base/net_util.h"
     26 #include "net/url_request/url_fetcher.h"
     27 #include "net/url_request/url_request_status.h"
     28 
     29 
     30 const char GoogleURLTracker::kDefaultGoogleHomepage[] =
     31     "http://www.google.com/";
     32 const char GoogleURLTracker::kSearchDomainCheckURL[] =
     33     "https://www.google.com/searchdomaincheck?format=url&type=chrome";
     34 
     35 GoogleURLTracker::GoogleURLTracker(
     36     Profile* profile,
     37     scoped_ptr<GoogleURLTrackerNavigationHelper> nav_helper,
     38     Mode mode)
     39     : profile_(profile),
     40       nav_helper_(nav_helper.Pass()),
     41       infobar_creator_(base::Bind(&GoogleURLTrackerInfoBarDelegate::Create)),
     42       google_url_(mode == UNIT_TEST_MODE ? kDefaultGoogleHomepage :
     43           profile->GetPrefs()->GetString(prefs::kLastKnownGoogleURL)),
     44       weak_ptr_factory_(this),
     45       fetcher_id_(0),
     46       in_startup_sleep_(true),
     47       already_fetched_(false),
     48       need_to_fetch_(false),
     49       need_to_prompt_(false),
     50       search_committed_(false) {
     51   net::NetworkChangeNotifier::AddIPAddressObserver(this);
     52   nav_helper_->SetGoogleURLTracker(this);
     53 
     54   // Because this function can be called during startup, when kicking off a URL
     55   // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully
     56   // long enough to be after startup, but still get results back quickly.
     57   // Ideally, instead of this timer, we'd do something like "check if the
     58   // browser is starting up, and if so, come back later", but there is currently
     59   // no function to do this.
     60   //
     61   // In UNIT_TEST mode, where we want to explicitly control when the tracker
     62   // "wakes up", we do nothing at all.
     63   if (mode == NORMAL_MODE) {
     64     static const int kStartFetchDelayMS = 5000;
     65     base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
     66         base::Bind(&GoogleURLTracker::FinishSleep,
     67                    weak_ptr_factory_.GetWeakPtr()),
     68         base::TimeDelta::FromMilliseconds(kStartFetchDelayMS));
     69   }
     70 }
     71 
     72 GoogleURLTracker::~GoogleURLTracker() {
     73   // We should only reach here after any tabs and their infobars have been torn
     74   // down.
     75   DCHECK(entry_map_.empty());
     76 }
     77 
     78 // static
     79 GURL GoogleURLTracker::GoogleURL(Profile* profile) {
     80   const GoogleURLTracker* tracker =
     81       GoogleURLTrackerFactory::GetForProfile(profile);
     82   return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage);
     83 }
     84 
     85 // static
     86 void GoogleURLTracker::RequestServerCheck(Profile* profile, bool force) {
     87   GoogleURLTracker* tracker = GoogleURLTrackerFactory::GetForProfile(profile);
     88   // If the tracker already has a fetcher, SetNeedToFetch() is unnecessary, and
     89   // changing |already_fetched_| is wrong.
     90   if (tracker && !tracker->fetcher_) {
     91     if (force)
     92       tracker->already_fetched_ = false;
     93     tracker->SetNeedToFetch();
     94   }
     95 }
     96 
     97 // static
     98 void GoogleURLTracker::GoogleURLSearchCommitted(Profile* profile) {
     99   GoogleURLTracker* tracker = GoogleURLTrackerFactory::GetForProfile(profile);
    100   if (tracker)
    101     tracker->SearchCommitted();
    102 }
    103 
    104 void GoogleURLTracker::AcceptGoogleURL(bool redo_searches) {
    105   UpdatedDetails urls(google_url_, fetched_google_url_);
    106   google_url_ = fetched_google_url_;
    107   PrefService* prefs = profile_->GetPrefs();
    108   prefs->SetString(prefs::kLastKnownGoogleURL, google_url_.spec());
    109   prefs->SetString(prefs::kLastPromptedGoogleURL, google_url_.spec());
    110   content::NotificationService::current()->Notify(
    111       chrome::NOTIFICATION_GOOGLE_URL_UPDATED,
    112       content::Source<Profile>(profile_),
    113       content::Details<UpdatedDetails>(&urls));
    114   need_to_prompt_ = false;
    115   CloseAllEntries(redo_searches);
    116 }
    117 
    118 void GoogleURLTracker::CancelGoogleURL() {
    119   profile_->GetPrefs()->SetString(prefs::kLastPromptedGoogleURL,
    120                                   fetched_google_url_.spec());
    121   need_to_prompt_ = false;
    122   CloseAllEntries(false);
    123 }
    124 
    125 void GoogleURLTracker::OnURLFetchComplete(const net::URLFetcher* source) {
    126   // Delete the fetcher on this function's exit.
    127   scoped_ptr<net::URLFetcher> clean_up_fetcher(fetcher_.release());
    128 
    129   // Don't update the URL if the request didn't succeed.
    130   if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) {
    131     already_fetched_ = false;
    132     return;
    133   }
    134 
    135   // See if the response data was valid.  It should be
    136   // "<scheme>://[www.]google.<TLD>/".
    137   std::string url_str;
    138   source->GetResponseAsString(&url_str);
    139   TrimWhitespace(url_str, TRIM_ALL, &url_str);
    140   GURL url(url_str);
    141   if (!url.is_valid() || (url.path().length() > 1) || url.has_query() ||
    142       url.has_ref() ||
    143       !google_util::IsGoogleDomainUrl(url, google_util::DISALLOW_SUBDOMAIN,
    144                                       google_util::DISALLOW_NON_STANDARD_PORTS))
    145     return;
    146 
    147   std::swap(url, fetched_google_url_);
    148   GURL last_prompted_url(
    149       profile_->GetPrefs()->GetString(prefs::kLastPromptedGoogleURL));
    150 
    151   if (last_prompted_url.is_empty()) {
    152     // On the very first run of Chrome, when we've never looked up the URL at
    153     // all, we should just silently switch over to whatever we get immediately.
    154     AcceptGoogleURL(true);  // Arg is irrelevant.
    155     return;
    156   }
    157 
    158   base::string16 fetched_host(net::StripWWWFromHost(fetched_google_url_));
    159   if (fetched_google_url_ == google_url_) {
    160     // Either the user has continually been on this URL, or we prompted for a
    161     // different URL but have now changed back before they responded to any of
    162     // the prompts.  In this latter case we want to close any infobars and stop
    163     // prompting.
    164     CancelGoogleURL();
    165   } else if (fetched_host == net::StripWWWFromHost(google_url_)) {
    166     // Similar to the above case, but this time the new URL differs from the
    167     // existing one, probably due to switching between HTTP and HTTPS searching.
    168     // Like before we want to close any infobars and stop prompting; we also
    169     // want to silently accept the change in scheme.  We don't redo open
    170     // searches so as to avoid suddenly changing a page the user might be
    171     // interacting with; it's enough to simply get future searches right.
    172     AcceptGoogleURL(false);
    173   } else if (fetched_host == net::StripWWWFromHost(last_prompted_url)) {
    174     // We've re-fetched a TLD the user previously turned down.  Although the new
    175     // URL might have a different scheme than the old, we want to preserve the
    176     // user's decision.  Note that it's possible that, like in the above two
    177     // cases, we fetched yet another different URL in the meantime, which we
    178     // have infobars prompting about; in this case, as in those above, we want
    179     // to go ahead and close the infobars and stop prompting, since we've
    180     // switched back away from that URL.
    181     CancelGoogleURL();
    182   } else {
    183     // We've fetched a URL with a different TLD than the user is currently using
    184     // or was previously prompted about.  This means we need to prompt again.
    185     need_to_prompt_ = true;
    186 
    187     // As in all the above cases, there could be infobars prompting about some
    188     // URL.  If these URLs have the same TLD (e.g. for scheme changes), we can
    189     // simply leave the existing infobars open as their messages will still be
    190     // accurate.  Otherwise we go ahead and close them because we need to
    191     // display a new message.
    192     // Note: |url| is the previous |fetched_google_url_|.
    193     if (url.is_valid() && (fetched_host != net::StripWWWFromHost(url)))
    194       CloseAllEntries(false);
    195   }
    196 }
    197 
    198 void GoogleURLTracker::OnIPAddressChanged() {
    199   already_fetched_ = false;
    200   StartFetchIfDesirable();
    201 }
    202 
    203 void GoogleURLTracker::Shutdown() {
    204   nav_helper_.reset();
    205   weak_ptr_factory_.InvalidateWeakPtrs();
    206   fetcher_.reset();
    207   net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
    208 }
    209 
    210 void GoogleURLTracker::DeleteMapEntryForService(
    211     const InfoBarService* infobar_service) {
    212   // WARNING: |infobar_service| may point to a deleted object.  Do not
    213   // dereference it!  See OnTabClosed().
    214   EntryMap::iterator i(entry_map_.find(infobar_service));
    215   DCHECK(i != entry_map_.end());
    216   GoogleURLTrackerMapEntry* map_entry = i->second;
    217 
    218   UnregisterForEntrySpecificNotifications(*map_entry, false);
    219   entry_map_.erase(i);
    220   delete map_entry;
    221 }
    222 
    223 void GoogleURLTracker::SetNeedToFetch() {
    224   need_to_fetch_ = true;
    225   StartFetchIfDesirable();
    226 }
    227 
    228 void GoogleURLTracker::FinishSleep() {
    229   in_startup_sleep_ = false;
    230   StartFetchIfDesirable();
    231 }
    232 
    233 void GoogleURLTracker::StartFetchIfDesirable() {
    234   // Bail if a fetch isn't appropriate right now.  This function will be called
    235   // again each time one of the preconditions changes, so we'll fetch
    236   // immediately once all of them are met.
    237   //
    238   // See comments in header on the class, on RequestServerCheck(), and on the
    239   // various members here for more detail on exactly what the conditions are.
    240   if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_)
    241     return;
    242 
    243   // Some switches should disable the Google URL tracker entirely.  If we can't
    244   // do background networking, we can't do the necessary fetch, and if the user
    245   // specified a Google base URL manually, we shouldn't bother to look up any
    246   // alternatives or offer to switch to them.
    247   if (CommandLine::ForCurrentProcess()->HasSwitch(
    248       switches::kDisableBackgroundNetworking) ||
    249       CommandLine::ForCurrentProcess()->HasSwitch(switches::kGoogleBaseURL))
    250     return;
    251 
    252   std::string fetch_url = CommandLine::ForCurrentProcess()->
    253       GetSwitchValueASCII(switches::kGoogleSearchDomainCheckURL);
    254   if (fetch_url.empty())
    255     fetch_url = kSearchDomainCheckURL;
    256 
    257   already_fetched_ = true;
    258   fetcher_.reset(net::URLFetcher::Create(fetcher_id_, GURL(fetch_url),
    259                                          net::URLFetcher::GET, this));
    260   ++fetcher_id_;
    261   // We don't want this fetch to set new entries in the cache or cookies, lest
    262   // we alarm the user.
    263   fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE |
    264                          net::LOAD_DO_NOT_SAVE_COOKIES);
    265   fetcher_->SetRequestContext(profile_->GetRequestContext());
    266 
    267   // Configure to max_retries at most kMaxRetries times for 5xx errors.
    268   static const int kMaxRetries = 5;
    269   fetcher_->SetMaxRetriesOn5xx(kMaxRetries);
    270 
    271   fetcher_->Start();
    272 }
    273 
    274 void GoogleURLTracker::SearchCommitted() {
    275   if (need_to_prompt_) {
    276     search_committed_ = true;
    277     // These notifications will fire a bit later in the same call chain we're
    278     // currently in.
    279     if (!nav_helper_->IsListeningForNavigationStart())
    280       nav_helper_->SetListeningForNavigationStart(true);
    281   }
    282 }
    283 
    284 void GoogleURLTracker::OnNavigationPending(
    285     content::NavigationController* navigation_controller,
    286     InfoBarService* infobar_service,
    287     int pending_id) {
    288   EntryMap::iterator i(entry_map_.find(infobar_service));
    289 
    290   if (search_committed_) {
    291     search_committed_ = false;
    292     // Whether there's an existing infobar or not, we need to listen for the
    293     // load to commit, so we can show and/or update the infobar when it does.
    294     // (We may already be registered for this if there is an existing infobar
    295     // that had a previous pending search that hasn't yet committed.)
    296     if (!nav_helper_->IsListeningForNavigationCommit(navigation_controller)) {
    297       nav_helper_->SetListeningForNavigationCommit(navigation_controller,
    298                                                    true);
    299     }
    300     if (i == entry_map_.end()) {
    301       // This is a search on a tab that doesn't have one of our infobars, so
    302       // prepare to add one.  Note that we only listen for the tab's destruction
    303       // on this path; if there was already a map entry, then either it doesn't
    304       // yet have an infobar and we're already registered for this, or it has an
    305       // infobar and the infobar's owner will handle tearing it down when the
    306       // tab is destroyed.
    307       nav_helper_->SetListeningForTabDestruction(navigation_controller, true);
    308       entry_map_.insert(std::make_pair(
    309           infobar_service,
    310           new GoogleURLTrackerMapEntry(this, infobar_service,
    311                                        navigation_controller)));
    312     } else if (i->second->has_infobar_delegate()) {
    313       // This is a new search on a tab where we already have an infobar.
    314       i->second->infobar_delegate()->set_pending_id(pending_id);
    315     }
    316   } else if (i != entry_map_.end()){
    317     if (i->second->has_infobar_delegate()) {
    318       // This is a non-search navigation on a tab with an infobar.  If there was
    319       // a previous pending search on this tab, this means it won't commit, so
    320       // undo anything we did in response to seeing that.  Note that if there
    321       // was no pending search on this tab, these statements are effectively a
    322       // no-op.
    323       //
    324       // If this navigation actually commits, that will trigger the infobar's
    325       // owner to expire the infobar if need be.  If it doesn't commit, then
    326       // simply leaving the infobar as-is will have been the right thing.
    327       UnregisterForEntrySpecificNotifications(*i->second, false);
    328       i->second->infobar_delegate()->set_pending_id(0);
    329     } else {
    330       // Non-search navigation on a tab with an entry that has not yet created
    331       // an infobar.  This means the original search won't commit, so delete the
    332       // entry.
    333       i->second->Close(false);
    334     }
    335   } else {
    336     // Non-search navigation on a tab without an infobars.  This is irrelevant
    337     // to us.
    338   }
    339 }
    340 
    341 void GoogleURLTracker::OnNavigationCommitted(InfoBarService* infobar_service,
    342                                              const GURL& search_url) {
    343   EntryMap::iterator i(entry_map_.find(infobar_service));
    344   DCHECK(i != entry_map_.end());
    345   GoogleURLTrackerMapEntry* map_entry = i->second;
    346   DCHECK(search_url.is_valid());
    347 
    348   UnregisterForEntrySpecificNotifications(*map_entry, true);
    349   if (map_entry->has_infobar_delegate()) {
    350     map_entry->infobar_delegate()->Update(search_url);
    351   } else {
    352     InfoBar* infobar = infobar_creator_.Run(infobar_service, this, search_url);
    353     if (infobar) {
    354       map_entry->SetInfoBarDelegate(
    355           static_cast<GoogleURLTrackerInfoBarDelegate*>(infobar->delegate()));
    356     } else {
    357       map_entry->Close(false);
    358     }
    359   }
    360 }
    361 
    362 void GoogleURLTracker::OnTabClosed(
    363     content::NavigationController* navigation_controller) {
    364   // Because InfoBarService tears itself down on tab destruction, it's possible
    365   // to get a non-NULL InfoBarService pointer here, depending on which order
    366   // notifications fired in.  Likewise, the pointer in |entry_map_| (and in its
    367   // associated MapEntry) may point to deleted memory.  Therefore, if we were to
    368   // access the InfoBarService* we have for this tab, we'd need to ensure we
    369   // just looked at the raw pointer value, and never dereferenced it.  This
    370   // function doesn't need to do even that, but others in the call chain from
    371   // here might (and have comments pointing back here).
    372   for (EntryMap::iterator i(entry_map_.begin()); i != entry_map_.end(); ++i) {
    373     if (i->second->navigation_controller() == navigation_controller) {
    374       i->second->Close(false);
    375       return;
    376     }
    377   }
    378   NOTREACHED();
    379 }
    380 
    381 void GoogleURLTracker::CloseAllEntries(bool redo_searches) {
    382   // Delete all entries, whether they have infobars or not.
    383   while (!entry_map_.empty())
    384     entry_map_.begin()->second->Close(redo_searches);
    385 }
    386 
    387 void GoogleURLTracker::UnregisterForEntrySpecificNotifications(
    388     const GoogleURLTrackerMapEntry& map_entry,
    389     bool must_be_listening_for_commit) {
    390   // For tabs with map entries but no infobars, we should always be listening
    391   // for both these notifications.  For tabs with infobars, we may be listening
    392   // for navigation commits if the user has performed a new search on this tab.
    393   if (nav_helper_->IsListeningForNavigationCommit(
    394           map_entry.navigation_controller())) {
    395     nav_helper_->SetListeningForNavigationCommit(
    396         map_entry.navigation_controller(), false);
    397   } else {
    398     DCHECK(!must_be_listening_for_commit);
    399     DCHECK(map_entry.has_infobar_delegate());
    400   }
    401   const bool registered_for_tab_destruction =
    402       nav_helper_->IsListeningForTabDestruction(
    403           map_entry.navigation_controller());
    404   DCHECK_NE(registered_for_tab_destruction, map_entry.has_infobar_delegate());
    405   if (registered_for_tab_destruction) {
    406     nav_helper_->SetListeningForTabDestruction(
    407         map_entry.navigation_controller(), false);
    408   }
    409 
    410   // Our global listeners for these other notifications should be in place iff
    411   // we have any tabs still listening for commits.  These tabs either have no
    412   // infobars or have received new pending searches atop existing infobars; in
    413   // either case we want to catch subsequent pending non-search navigations.
    414   // See the various cases inside OnNavigationPending().
    415   for (EntryMap::const_iterator i(entry_map_.begin()); i != entry_map_.end();
    416        ++i) {
    417     if (nav_helper_->IsListeningForNavigationCommit(
    418             i->second->navigation_controller())) {
    419       DCHECK(nav_helper_->IsListeningForNavigationStart());
    420       return;
    421     }
    422   }
    423   if (nav_helper_->IsListeningForNavigationStart()) {
    424     DCHECK(!search_committed_);
    425     nav_helper_->SetListeningForNavigationStart(false);
    426   }
    427 }
    428