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 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 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 51 bool GoogleURLTrackerInfoBarDelegate::Accept() { 52 google_url_tracker_->AcceptGoogleURL(new_google_url_); 53 google_url_tracker_->RedoSearch(); 54 return true; 55 } 56 57 bool GoogleURLTrackerInfoBarDelegate::Cancel() { 58 google_url_tracker_->CancelGoogleURL(new_google_url_); 59 return true; 60 } 61 62 void GoogleURLTrackerInfoBarDelegate::InfoBarClosed() { 63 google_url_tracker_->InfoBarClosed(); 64 delete this; 65 } 66 67 GoogleURLTrackerInfoBarDelegate::~GoogleURLTrackerInfoBarDelegate() { 68 } 69 70 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 76 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 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 111 GoogleURLTracker::~GoogleURLTracker() { 112 runnable_method_factory_.RevokeAll(); 113 net::NetworkChangeNotifier::RemoveIPAddressObserver(this); 114 } 115 116 // static 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 124 void GoogleURLTracker::RequestServerCheck() { 125 GoogleURLTracker* const tracker = g_browser_process->google_url_tracker(); 126 if (tracker) 127 tracker->SetNeedToFetch(); 128 } 129 130 // static 131 void GoogleURLTracker::RegisterPrefs(PrefService* prefs) { 132 prefs->RegisterStringPref(prefs::kLastKnownGoogleURL, 133 kDefaultGoogleHomepage); 134 prefs->RegisterStringPref(prefs::kLastPromptedGoogleURL, std::string()); 135 } 136 137 // static 138 void GoogleURLTracker::GoogleURLSearchCommitted() { 139 GoogleURLTracker* tracker = g_browser_process->google_url_tracker(); 140 if (tracker) 141 tracker->SearchCommitted(); 142 } 143 144 void GoogleURLTracker::SetNeedToFetch() { 145 need_to_fetch_ = true; 146 StartFetchIfDesirable(); 147 } 148 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 168 void GoogleURLTracker::FinishSleep() { 169 in_startup_sleep_ = false; 170 StartFetchIfDesirable(); 171 } 172 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 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 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 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 274 void GoogleURLTracker::InfoBarClosed() { 275 registrar_.RemoveAll(); 276 controller_ = NULL; 277 infobar_ = NULL; 278 search_url_ = GURL(); 279 } 280 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 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 316 void GoogleURLTracker::OnIPAddressChanged() { 317 already_fetched_ = false; 318 StartFetchIfDesirable(); 319 } 320 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 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 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 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