• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
CreateInfobar(TabContents * tab_contents,GoogleURLTracker * google_url_tracker,const GURL & new_google_url)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 
GoogleURLTrackerInfoBarDelegate(TabContents * tab_contents,GoogleURLTracker * google_url_tracker,const GURL & new_google_url)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 
Accept()51 bool GoogleURLTrackerInfoBarDelegate::Accept() {
52   google_url_tracker_->AcceptGoogleURL(new_google_url_);
53   google_url_tracker_->RedoSearch();
54   return true;
55 }
56 
Cancel()57 bool GoogleURLTrackerInfoBarDelegate::Cancel() {
58   google_url_tracker_->CancelGoogleURL(new_google_url_);
59   return true;
60 }
61 
InfoBarClosed()62 void GoogleURLTrackerInfoBarDelegate::InfoBarClosed() {
63   google_url_tracker_->InfoBarClosed();
64   delete this;
65 }
66 
~GoogleURLTrackerInfoBarDelegate()67 GoogleURLTrackerInfoBarDelegate::~GoogleURLTrackerInfoBarDelegate() {
68 }
69 
GetMessageText() const70 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 
GetButtonLabel(InfoBarButton button) const76 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 
GoogleURLTracker()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 
~GoogleURLTracker()111 GoogleURLTracker::~GoogleURLTracker() {
112   runnable_method_factory_.RevokeAll();
113   net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
114 }
115 
116 // static
GoogleURL()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
RequestServerCheck()124 void GoogleURLTracker::RequestServerCheck() {
125   GoogleURLTracker* const tracker = g_browser_process->google_url_tracker();
126   if (tracker)
127     tracker->SetNeedToFetch();
128 }
129 
130 // static
RegisterPrefs(PrefService * prefs)131 void GoogleURLTracker::RegisterPrefs(PrefService* prefs) {
132   prefs->RegisterStringPref(prefs::kLastKnownGoogleURL,
133                             kDefaultGoogleHomepage);
134   prefs->RegisterStringPref(prefs::kLastPromptedGoogleURL, std::string());
135 }
136 
137 // static
GoogleURLSearchCommitted()138 void GoogleURLTracker::GoogleURLSearchCommitted() {
139   GoogleURLTracker* tracker = g_browser_process->google_url_tracker();
140   if (tracker)
141     tracker->SearchCommitted();
142 }
143 
SetNeedToFetch()144 void GoogleURLTracker::SetNeedToFetch() {
145   need_to_fetch_ = true;
146   StartFetchIfDesirable();
147 }
148 
QueueWakeupTask()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 
FinishSleep()168 void GoogleURLTracker::FinishSleep() {
169   in_startup_sleep_ = false;
170   StartFetchIfDesirable();
171 }
172 
StartFetchIfDesirable()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 
OnURLFetchComplete(const URLFetcher * source,const GURL & url,const net::URLRequestStatus & status,int response_code,const ResponseCookies & cookies,const std::string & data)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 
AcceptGoogleURL(const GURL & new_google_url)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 
CancelGoogleURL(const GURL & new_google_url)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 
InfoBarClosed()274 void GoogleURLTracker::InfoBarClosed() {
275   registrar_.RemoveAll();
276   controller_ = NULL;
277   infobar_ = NULL;
278   search_url_ = GURL();
279 }
280 
RedoSearch()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 
Observe(NotificationType type,const NotificationSource & source,const NotificationDetails & details)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 
OnIPAddressChanged()316 void GoogleURLTracker::OnIPAddressChanged() {
317   already_fetched_ = false;
318   StartFetchIfDesirable();
319 }
320 
SearchCommitted()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 
OnNavigationPending(const NotificationSource & source,const GURL & pending_url)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 
OnNavigationCommittedOrTabClosed(TabContents * tab_contents,NotificationType::Type type)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 
ShowGoogleURLInfoBarIfNecessary(TabContents * tab_contents)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