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
27namespace {
28
29InfoBarDelegate* 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
42GoogleURLTrackerInfoBarDelegate::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
51bool GoogleURLTrackerInfoBarDelegate::Accept() {
52  google_url_tracker_->AcceptGoogleURL(new_google_url_);
53  google_url_tracker_->RedoSearch();
54  return true;
55}
56
57bool GoogleURLTrackerInfoBarDelegate::Cancel() {
58  google_url_tracker_->CancelGoogleURL(new_google_url_);
59  return true;
60}
61
62void GoogleURLTrackerInfoBarDelegate::InfoBarClosed() {
63  google_url_tracker_->InfoBarClosed();
64  delete this;
65}
66
67GoogleURLTrackerInfoBarDelegate::~GoogleURLTrackerInfoBarDelegate() {
68}
69
70string16 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
76string16 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
86const char GoogleURLTracker::kDefaultGoogleHomepage[] =
87    "http://www.google.com/";
88const char GoogleURLTracker::kSearchDomainCheckURL[] =
89    "https://www.google.com/searchdomaincheck?format=domain&type=chrome";
90
91GoogleURLTracker::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
111GoogleURLTracker::~GoogleURLTracker() {
112  runnable_method_factory_.RevokeAll();
113  net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
114}
115
116// static
117GURL 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
124void GoogleURLTracker::RequestServerCheck() {
125  GoogleURLTracker* const tracker = g_browser_process->google_url_tracker();
126  if (tracker)
127    tracker->SetNeedToFetch();
128}
129
130// static
131void GoogleURLTracker::RegisterPrefs(PrefService* prefs) {
132  prefs->RegisterStringPref(prefs::kLastKnownGoogleURL,
133                            kDefaultGoogleHomepage);
134  prefs->RegisterStringPref(prefs::kLastPromptedGoogleURL, std::string());
135}
136
137// static
138void GoogleURLTracker::GoogleURLSearchCommitted() {
139  GoogleURLTracker* tracker = g_browser_process->google_url_tracker();
140  if (tracker)
141    tracker->SearchCommitted();
142}
143
144void GoogleURLTracker::SetNeedToFetch() {
145  need_to_fetch_ = true;
146  StartFetchIfDesirable();
147}
148
149void 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
168void GoogleURLTracker::FinishSleep() {
169  in_startup_sleep_ = false;
170  StartFetchIfDesirable();
171}
172
173void 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
205void 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
256void 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
268void 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
274void GoogleURLTracker::InfoBarClosed() {
275  registrar_.RemoveAll();
276  controller_ = NULL;
277  infobar_ = NULL;
278  search_url_ = GURL();
279}
280
281void 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
293void 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
316void GoogleURLTracker::OnIPAddressChanged() {
317  already_fetched_ = false;
318  StartFetchIfDesirable();
319}
320
321void 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
330void 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
344void 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
357void 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