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