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