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
24const char GoogleURLTracker::kDefaultGoogleHomepage[] =
25    "http://www.google.com/";
26const char GoogleURLTracker::kSearchDomainCheckURL[] =
27    "https://www.google.com/searchdomaincheck?format=url&type=chrome";
28
29GoogleURLTracker::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::AddNetworkChangeObserver(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
63GoogleURLTracker::~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
69void 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
79void 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
89void 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();
96
97  need_to_prompt_ = false;
98  CloseAllEntries(redo_searches);
99}
100
101void GoogleURLTracker::CancelGoogleURL() {
102  client_->GetPrefs()->SetString(prefs::kLastPromptedGoogleURL,
103                                 fetched_google_url_.spec());
104  need_to_prompt_ = false;
105  CloseAllEntries(false);
106}
107
108void 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
182void GoogleURLTracker::OnNetworkChanged(
183    net::NetworkChangeNotifier::ConnectionType type) {
184  // Ignore destructive signals.
185  if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
186    return;
187  already_fetched_ = false;
188  StartFetchIfDesirable();
189}
190
191void GoogleURLTracker::Shutdown() {
192  client_.reset();
193  fetcher_.reset();
194  weak_ptr_factory_.InvalidateWeakPtrs();
195  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
196}
197
198void GoogleURLTracker::DeleteMapEntryForManager(
199    const infobars::InfoBarManager* infobar_manager) {
200  // WARNING: |infobar_manager| may point to a deleted object.  Do not
201  // dereference it!  See OnTabClosed().
202  EntryMap::iterator i(entry_map_.find(infobar_manager));
203  DCHECK(i != entry_map_.end());
204  GoogleURLTrackerMapEntry* map_entry = i->second;
205
206  UnregisterForEntrySpecificNotifications(map_entry, false);
207  entry_map_.erase(i);
208  delete map_entry;
209}
210
211void GoogleURLTracker::SetNeedToFetch() {
212  need_to_fetch_ = true;
213  StartFetchIfDesirable();
214}
215
216void GoogleURLTracker::FinishSleep() {
217  in_startup_sleep_ = false;
218  StartFetchIfDesirable();
219}
220
221void GoogleURLTracker::StartFetchIfDesirable() {
222  // Bail if a fetch isn't appropriate right now.  This function will be called
223  // again each time one of the preconditions changes, so we'll fetch
224  // immediately once all of them are met.
225  //
226  // See comments in header on the class, on RequestServerCheck(), and on the
227  // various members here for more detail on exactly what the conditions are.
228  if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_)
229    return;
230
231  // Some switches should disable the Google URL tracker entirely.  If we can't
232  // do background networking, we can't do the necessary fetch, and if the user
233  // specified a Google base URL manually, we shouldn't bother to look up any
234  // alternatives or offer to switch to them.
235  if (!client_->IsBackgroundNetworkingEnabled() ||
236      CommandLine::ForCurrentProcess()->HasSwitch(switches::kGoogleBaseURL))
237    return;
238
239  already_fetched_ = true;
240  fetcher_.reset(net::URLFetcher::Create(
241      fetcher_id_, GURL(kSearchDomainCheckURL), net::URLFetcher::GET, this));
242  ++fetcher_id_;
243  // We don't want this fetch to set new entries in the cache or cookies, lest
244  // we alarm the user.
245  fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE |
246                         net::LOAD_DO_NOT_SAVE_COOKIES);
247  fetcher_->SetRequestContext(client_->GetRequestContext());
248
249  // Configure to retry at most kMaxRetries times for 5xx errors.
250  static const int kMaxRetries = 5;
251  fetcher_->SetMaxRetriesOn5xx(kMaxRetries);
252
253  // Also retry kMaxRetries times on network change errors. A network change can
254  // propagate through Chrome in various stages, so it's possible for this code
255  // to be reached via OnNetworkChanged(), and then have the fetch we kick off
256  // be canceled due to e.g. the DNS server changing at a later time. In general
257  // it's not possible to ensure that by the time we reach here any requests we
258  // start won't be canceled in this fashion, so retrying is the best we can do.
259  fetcher_->SetAutomaticallyRetryOnNetworkChanges(kMaxRetries);
260
261  fetcher_->Start();
262}
263
264void GoogleURLTracker::OnNavigationPending(
265    scoped_ptr<GoogleURLTrackerNavigationHelper> nav_helper,
266    infobars::InfoBarManager* infobar_manager,
267    int pending_id) {
268  GoogleURLTrackerMapEntry* map_entry = NULL;
269
270  EntryMap::iterator i(entry_map_.find(infobar_manager));
271  if (i != entry_map_.end())
272    map_entry = i->second;
273
274  if (search_committed_) {
275    search_committed_ = false;
276    if (!map_entry) {
277      // This is a search on a tab that doesn't have one of our infobars, so
278      // prepare to add one.  Note that we only listen for the tab's destruction
279      // on this path; if there was already a map entry, then either it doesn't
280      // yet have an infobar and we're already registered for this, or it has an
281      // infobar and the infobar's owner will handle tearing it down when the
282      // tab is destroyed.
283      map_entry = new GoogleURLTrackerMapEntry(
284          this, infobar_manager, nav_helper.Pass());
285      map_entry->navigation_helper()->SetListeningForTabDestruction(true);
286      entry_map_.insert(std::make_pair(infobar_manager, map_entry));
287    } else if (map_entry->infobar_delegate()) {
288      // This is a new search on a tab where we already have an infobar.
289      map_entry->infobar_delegate()->set_pending_id(pending_id);
290    }
291
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 (!map_entry->navigation_helper()->IsListeningForNavigationCommit())
297      map_entry->navigation_helper()->SetListeningForNavigationCommit(true);
298  } else if (map_entry) {
299    if (map_entry->has_infobar_delegate()) {
300      // This is a non-search navigation on a tab with an infobar.  If there was
301      // a previous pending search on this tab, this means it won't commit, so
302      // undo anything we did in response to seeing that.  Note that if there
303      // was no pending search on this tab, these statements are effectively a
304      // no-op.
305      //
306      // If this navigation actually commits, that will trigger the infobar's
307      // owner to expire the infobar if need be.  If it doesn't commit, then
308      // simply leaving the infobar as-is will have been the right thing.
309      UnregisterForEntrySpecificNotifications(map_entry, false);
310      map_entry->infobar_delegate()->set_pending_id(0);
311    } else {
312      // Non-search navigation on a tab with an entry that has not yet created
313      // an infobar.  This means the original search won't commit, so delete the
314      // entry.
315      map_entry->Close(false);
316    }
317  } else {
318    // Non-search navigation on a tab without an infobars.  This is irrelevant
319    // to us.
320  }
321}
322
323void GoogleURLTracker::OnNavigationCommitted(
324    infobars::InfoBarManager* infobar_manager,
325    const GURL& search_url) {
326  EntryMap::iterator i(entry_map_.find(infobar_manager));
327  DCHECK(i != entry_map_.end());
328  GoogleURLTrackerMapEntry* map_entry = i->second;
329  DCHECK(search_url.is_valid());
330
331  UnregisterForEntrySpecificNotifications(map_entry, true);
332  if (map_entry->has_infobar_delegate()) {
333    map_entry->infobar_delegate()->Update(search_url);
334  } else {
335    infobars::InfoBar* infobar = GoogleURLTrackerInfoBarDelegate::Create(
336        infobar_manager, this, search_url);
337    if (infobar) {
338      map_entry->SetInfoBarDelegate(
339          static_cast<GoogleURLTrackerInfoBarDelegate*>(infobar->delegate()));
340    } else {
341      map_entry->Close(false);
342    }
343  }
344}
345
346void GoogleURLTracker::OnTabClosed(
347    GoogleURLTrackerNavigationHelper* nav_helper) {
348  // Because InfoBarManager tears itself down on tab destruction, it's possible
349  // to get a non-NULL InfoBarManager pointer here, depending on which order
350  // notifications fired in.  Likewise, the pointer in |entry_map_| (and in its
351  // associated MapEntry) may point to deleted memory.  Therefore, if we were
352  // to access the InfoBarManager* we have for this tab, we'd need to ensure we
353  // just looked at the raw pointer value, and never dereferenced it.  This
354  // function doesn't need to do even that, but others in the call chain from
355  // here might (and have comments pointing back here).
356  for (EntryMap::iterator i(entry_map_.begin()); i != entry_map_.end(); ++i) {
357    if (i->second->navigation_helper() == nav_helper) {
358      i->second->Close(false);
359      return;
360    }
361  }
362  NOTREACHED();
363}
364
365scoped_ptr<GoogleURLTracker::Subscription> GoogleURLTracker::RegisterCallback(
366    const OnGoogleURLUpdatedCallback& cb) {
367  return callback_list_.Add(cb);
368}
369
370void GoogleURLTracker::CloseAllEntries(bool redo_searches) {
371  // Delete all entries, whether they have infobars or not.
372  while (!entry_map_.empty())
373    entry_map_.begin()->second->Close(redo_searches);
374}
375
376void GoogleURLTracker::UnregisterForEntrySpecificNotifications(
377    GoogleURLTrackerMapEntry* map_entry,
378    bool must_be_listening_for_commit) {
379  // For tabs with map entries but no infobars, we should always be listening
380  // for both these notifications.  For tabs with infobars, we may be listening
381  // for navigation commits if the user has performed a new search on this tab.
382  if (map_entry->navigation_helper()->IsListeningForNavigationCommit()) {
383    map_entry->navigation_helper()->SetListeningForNavigationCommit(false);
384  } else {
385    DCHECK(!must_be_listening_for_commit);
386    DCHECK(map_entry->has_infobar_delegate());
387  }
388  const bool registered_for_tab_destruction =
389      map_entry->navigation_helper()->IsListeningForTabDestruction();
390  DCHECK_NE(registered_for_tab_destruction, map_entry->has_infobar_delegate());
391  if (registered_for_tab_destruction) {
392    map_entry->navigation_helper()->SetListeningForTabDestruction(false);
393  }
394
395  // Our global listeners for these other notifications should be in place iff
396  // we have any tabs still listening for commits.  These tabs either have no
397  // infobars or have received new pending searches atop existing infobars; in
398  // either case we want to catch subsequent pending non-search navigations.
399  // See the various cases inside OnNavigationPending().
400  for (EntryMap::const_iterator i(entry_map_.begin()); i != entry_map_.end();
401       ++i) {
402    if (i->second->navigation_helper()->IsListeningForNavigationCommit()) {
403      DCHECK(client_->IsListeningForNavigationStart());
404      return;
405    }
406  }
407  if (client_->IsListeningForNavigationStart()) {
408    DCHECK(!search_committed_);
409    client_->SetListeningForNavigationStart(false);
410  }
411}
412
413void GoogleURLTracker::NotifyGoogleURLUpdated() {
414  callback_list_.Notify();
415}
416