1 // Copyright (c) 2012 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 "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 "chrome/browser/chrome_notification_types.h" 12 #include "chrome/browser/google/google_url_tracker_factory.h" 13 #include "chrome/browser/google/google_url_tracker_infobar_delegate.h" 14 #include "chrome/browser/google/google_url_tracker_navigation_helper.h" 15 #include "chrome/browser/google/google_util.h" 16 #include "chrome/browser/infobars/infobar.h" 17 #include "chrome/browser/infobars/infobar_service.h" 18 #include "chrome/browser/profiles/profile.h" 19 #include "chrome/common/chrome_switches.h" 20 #include "chrome/common/pref_names.h" 21 #include "content/public/browser/navigation_controller.h" 22 #include "content/public/browser/navigation_entry.h" 23 #include "content/public/browser/notification_service.h" 24 #include "net/base/load_flags.h" 25 #include "net/base/net_util.h" 26 #include "net/url_request/url_fetcher.h" 27 #include "net/url_request/url_request_status.h" 28 29 30 const char GoogleURLTracker::kDefaultGoogleHomepage[] = 31 "http://www.google.com/"; 32 const char GoogleURLTracker::kSearchDomainCheckURL[] = 33 "https://www.google.com/searchdomaincheck?format=url&type=chrome"; 34 35 GoogleURLTracker::GoogleURLTracker( 36 Profile* profile, 37 scoped_ptr<GoogleURLTrackerNavigationHelper> nav_helper, 38 Mode mode) 39 : profile_(profile), 40 nav_helper_(nav_helper.Pass()), 41 infobar_creator_(base::Bind(&GoogleURLTrackerInfoBarDelegate::Create)), 42 google_url_(mode == UNIT_TEST_MODE ? kDefaultGoogleHomepage : 43 profile->GetPrefs()->GetString(prefs::kLastKnownGoogleURL)), 44 weak_ptr_factory_(this), 45 fetcher_id_(0), 46 in_startup_sleep_(true), 47 already_fetched_(false), 48 need_to_fetch_(false), 49 need_to_prompt_(false), 50 search_committed_(false) { 51 net::NetworkChangeNotifier::AddIPAddressObserver(this); 52 nav_helper_->SetGoogleURLTracker(this); 53 54 // Because this function can be called during startup, when kicking off a URL 55 // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully 56 // long enough to be after startup, but still get results back quickly. 57 // Ideally, instead of this timer, we'd do something like "check if the 58 // browser is starting up, and if so, come back later", but there is currently 59 // no function to do this. 60 // 61 // In UNIT_TEST mode, where we want to explicitly control when the tracker 62 // "wakes up", we do nothing at all. 63 if (mode == NORMAL_MODE) { 64 static const int kStartFetchDelayMS = 5000; 65 base::MessageLoop::current()->PostDelayedTask(FROM_HERE, 66 base::Bind(&GoogleURLTracker::FinishSleep, 67 weak_ptr_factory_.GetWeakPtr()), 68 base::TimeDelta::FromMilliseconds(kStartFetchDelayMS)); 69 } 70 } 71 72 GoogleURLTracker::~GoogleURLTracker() { 73 // We should only reach here after any tabs and their infobars have been torn 74 // down. 75 DCHECK(entry_map_.empty()); 76 } 77 78 // static 79 GURL GoogleURLTracker::GoogleURL(Profile* profile) { 80 const GoogleURLTracker* tracker = 81 GoogleURLTrackerFactory::GetForProfile(profile); 82 return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage); 83 } 84 85 // static 86 void GoogleURLTracker::RequestServerCheck(Profile* profile, bool force) { 87 GoogleURLTracker* tracker = GoogleURLTrackerFactory::GetForProfile(profile); 88 // If the tracker already has a fetcher, SetNeedToFetch() is unnecessary, and 89 // changing |already_fetched_| is wrong. 90 if (tracker && !tracker->fetcher_) { 91 if (force) 92 tracker->already_fetched_ = false; 93 tracker->SetNeedToFetch(); 94 } 95 } 96 97 // static 98 void GoogleURLTracker::GoogleURLSearchCommitted(Profile* profile) { 99 GoogleURLTracker* tracker = GoogleURLTrackerFactory::GetForProfile(profile); 100 if (tracker) 101 tracker->SearchCommitted(); 102 } 103 104 void GoogleURLTracker::AcceptGoogleURL(bool redo_searches) { 105 UpdatedDetails urls(google_url_, fetched_google_url_); 106 google_url_ = fetched_google_url_; 107 PrefService* prefs = profile_->GetPrefs(); 108 prefs->SetString(prefs::kLastKnownGoogleURL, google_url_.spec()); 109 prefs->SetString(prefs::kLastPromptedGoogleURL, google_url_.spec()); 110 content::NotificationService::current()->Notify( 111 chrome::NOTIFICATION_GOOGLE_URL_UPDATED, 112 content::Source<Profile>(profile_), 113 content::Details<UpdatedDetails>(&urls)); 114 need_to_prompt_ = false; 115 CloseAllEntries(redo_searches); 116 } 117 118 void GoogleURLTracker::CancelGoogleURL() { 119 profile_->GetPrefs()->SetString(prefs::kLastPromptedGoogleURL, 120 fetched_google_url_.spec()); 121 need_to_prompt_ = false; 122 CloseAllEntries(false); 123 } 124 125 void GoogleURLTracker::OnURLFetchComplete(const net::URLFetcher* source) { 126 // Delete the fetcher on this function's exit. 127 scoped_ptr<net::URLFetcher> clean_up_fetcher(fetcher_.release()); 128 129 // Don't update the URL if the request didn't succeed. 130 if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) { 131 already_fetched_ = false; 132 return; 133 } 134 135 // See if the response data was valid. It should be 136 // "<scheme>://[www.]google.<TLD>/". 137 std::string url_str; 138 source->GetResponseAsString(&url_str); 139 TrimWhitespace(url_str, TRIM_ALL, &url_str); 140 GURL url(url_str); 141 if (!url.is_valid() || (url.path().length() > 1) || url.has_query() || 142 url.has_ref() || 143 !google_util::IsGoogleDomainUrl(url, google_util::DISALLOW_SUBDOMAIN, 144 google_util::DISALLOW_NON_STANDARD_PORTS)) 145 return; 146 147 std::swap(url, fetched_google_url_); 148 GURL last_prompted_url( 149 profile_->GetPrefs()->GetString(prefs::kLastPromptedGoogleURL)); 150 151 if (last_prompted_url.is_empty()) { 152 // On the very first run of Chrome, when we've never looked up the URL at 153 // all, we should just silently switch over to whatever we get immediately. 154 AcceptGoogleURL(true); // Arg is irrelevant. 155 return; 156 } 157 158 base::string16 fetched_host(net::StripWWWFromHost(fetched_google_url_)); 159 if (fetched_google_url_ == google_url_) { 160 // Either the user has continually been on this URL, or we prompted for a 161 // different URL but have now changed back before they responded to any of 162 // the prompts. In this latter case we want to close any infobars and stop 163 // prompting. 164 CancelGoogleURL(); 165 } else if (fetched_host == net::StripWWWFromHost(google_url_)) { 166 // Similar to the above case, but this time the new URL differs from the 167 // existing one, probably due to switching between HTTP and HTTPS searching. 168 // Like before we want to close any infobars and stop prompting; we also 169 // want to silently accept the change in scheme. We don't redo open 170 // searches so as to avoid suddenly changing a page the user might be 171 // interacting with; it's enough to simply get future searches right. 172 AcceptGoogleURL(false); 173 } else if (fetched_host == net::StripWWWFromHost(last_prompted_url)) { 174 // We've re-fetched a TLD the user previously turned down. Although the new 175 // URL might have a different scheme than the old, we want to preserve the 176 // user's decision. Note that it's possible that, like in the above two 177 // cases, we fetched yet another different URL in the meantime, which we 178 // have infobars prompting about; in this case, as in those above, we want 179 // to go ahead and close the infobars and stop prompting, since we've 180 // switched back away from that URL. 181 CancelGoogleURL(); 182 } else { 183 // We've fetched a URL with a different TLD than the user is currently using 184 // or was previously prompted about. This means we need to prompt again. 185 need_to_prompt_ = true; 186 187 // As in all the above cases, there could be infobars prompting about some 188 // URL. If these URLs have the same TLD (e.g. for scheme changes), we can 189 // simply leave the existing infobars open as their messages will still be 190 // accurate. Otherwise we go ahead and close them because we need to 191 // display a new message. 192 // Note: |url| is the previous |fetched_google_url_|. 193 if (url.is_valid() && (fetched_host != net::StripWWWFromHost(url))) 194 CloseAllEntries(false); 195 } 196 } 197 198 void GoogleURLTracker::OnIPAddressChanged() { 199 already_fetched_ = false; 200 StartFetchIfDesirable(); 201 } 202 203 void GoogleURLTracker::Shutdown() { 204 nav_helper_.reset(); 205 weak_ptr_factory_.InvalidateWeakPtrs(); 206 fetcher_.reset(); 207 net::NetworkChangeNotifier::RemoveIPAddressObserver(this); 208 } 209 210 void GoogleURLTracker::DeleteMapEntryForService( 211 const InfoBarService* infobar_service) { 212 // WARNING: |infobar_service| may point to a deleted object. Do not 213 // dereference it! See OnTabClosed(). 214 EntryMap::iterator i(entry_map_.find(infobar_service)); 215 DCHECK(i != entry_map_.end()); 216 GoogleURLTrackerMapEntry* map_entry = i->second; 217 218 UnregisterForEntrySpecificNotifications(*map_entry, false); 219 entry_map_.erase(i); 220 delete map_entry; 221 } 222 223 void GoogleURLTracker::SetNeedToFetch() { 224 need_to_fetch_ = true; 225 StartFetchIfDesirable(); 226 } 227 228 void GoogleURLTracker::FinishSleep() { 229 in_startup_sleep_ = false; 230 StartFetchIfDesirable(); 231 } 232 233 void GoogleURLTracker::StartFetchIfDesirable() { 234 // Bail if a fetch isn't appropriate right now. This function will be called 235 // again each time one of the preconditions changes, so we'll fetch 236 // immediately once all of them are met. 237 // 238 // See comments in header on the class, on RequestServerCheck(), and on the 239 // various members here for more detail on exactly what the conditions are. 240 if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_) 241 return; 242 243 // Some switches should disable the Google URL tracker entirely. If we can't 244 // do background networking, we can't do the necessary fetch, and if the user 245 // specified a Google base URL manually, we shouldn't bother to look up any 246 // alternatives or offer to switch to them. 247 if (CommandLine::ForCurrentProcess()->HasSwitch( 248 switches::kDisableBackgroundNetworking) || 249 CommandLine::ForCurrentProcess()->HasSwitch(switches::kGoogleBaseURL)) 250 return; 251 252 std::string fetch_url = CommandLine::ForCurrentProcess()-> 253 GetSwitchValueASCII(switches::kGoogleSearchDomainCheckURL); 254 if (fetch_url.empty()) 255 fetch_url = kSearchDomainCheckURL; 256 257 already_fetched_ = true; 258 fetcher_.reset(net::URLFetcher::Create(fetcher_id_, GURL(fetch_url), 259 net::URLFetcher::GET, this)); 260 ++fetcher_id_; 261 // We don't want this fetch to set new entries in the cache or cookies, lest 262 // we alarm the user. 263 fetcher_->SetLoadFlags(net::LOAD_DISABLE_CACHE | 264 net::LOAD_DO_NOT_SAVE_COOKIES); 265 fetcher_->SetRequestContext(profile_->GetRequestContext()); 266 267 // Configure to max_retries at most kMaxRetries times for 5xx errors. 268 static const int kMaxRetries = 5; 269 fetcher_->SetMaxRetriesOn5xx(kMaxRetries); 270 271 fetcher_->Start(); 272 } 273 274 void GoogleURLTracker::SearchCommitted() { 275 if (need_to_prompt_) { 276 search_committed_ = true; 277 // These notifications will fire a bit later in the same call chain we're 278 // currently in. 279 if (!nav_helper_->IsListeningForNavigationStart()) 280 nav_helper_->SetListeningForNavigationStart(true); 281 } 282 } 283 284 void GoogleURLTracker::OnNavigationPending( 285 content::NavigationController* navigation_controller, 286 InfoBarService* infobar_service, 287 int pending_id) { 288 EntryMap::iterator i(entry_map_.find(infobar_service)); 289 290 if (search_committed_) { 291 search_committed_ = false; 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 (!nav_helper_->IsListeningForNavigationCommit(navigation_controller)) { 297 nav_helper_->SetListeningForNavigationCommit(navigation_controller, 298 true); 299 } 300 if (i == entry_map_.end()) { 301 // This is a search on a tab that doesn't have one of our infobars, so 302 // prepare to add one. Note that we only listen for the tab's destruction 303 // on this path; if there was already a map entry, then either it doesn't 304 // yet have an infobar and we're already registered for this, or it has an 305 // infobar and the infobar's owner will handle tearing it down when the 306 // tab is destroyed. 307 nav_helper_->SetListeningForTabDestruction(navigation_controller, true); 308 entry_map_.insert(std::make_pair( 309 infobar_service, 310 new GoogleURLTrackerMapEntry(this, infobar_service, 311 navigation_controller))); 312 } else if (i->second->has_infobar_delegate()) { 313 // This is a new search on a tab where we already have an infobar. 314 i->second->infobar_delegate()->set_pending_id(pending_id); 315 } 316 } else if (i != entry_map_.end()){ 317 if (i->second->has_infobar_delegate()) { 318 // This is a non-search navigation on a tab with an infobar. If there was 319 // a previous pending search on this tab, this means it won't commit, so 320 // undo anything we did in response to seeing that. Note that if there 321 // was no pending search on this tab, these statements are effectively a 322 // no-op. 323 // 324 // If this navigation actually commits, that will trigger the infobar's 325 // owner to expire the infobar if need be. If it doesn't commit, then 326 // simply leaving the infobar as-is will have been the right thing. 327 UnregisterForEntrySpecificNotifications(*i->second, false); 328 i->second->infobar_delegate()->set_pending_id(0); 329 } else { 330 // Non-search navigation on a tab with an entry that has not yet created 331 // an infobar. This means the original search won't commit, so delete the 332 // entry. 333 i->second->Close(false); 334 } 335 } else { 336 // Non-search navigation on a tab without an infobars. This is irrelevant 337 // to us. 338 } 339 } 340 341 void GoogleURLTracker::OnNavigationCommitted(InfoBarService* infobar_service, 342 const GURL& search_url) { 343 EntryMap::iterator i(entry_map_.find(infobar_service)); 344 DCHECK(i != entry_map_.end()); 345 GoogleURLTrackerMapEntry* map_entry = i->second; 346 DCHECK(search_url.is_valid()); 347 348 UnregisterForEntrySpecificNotifications(*map_entry, true); 349 if (map_entry->has_infobar_delegate()) { 350 map_entry->infobar_delegate()->Update(search_url); 351 } else { 352 InfoBar* infobar = infobar_creator_.Run(infobar_service, this, search_url); 353 if (infobar) { 354 map_entry->SetInfoBarDelegate( 355 static_cast<GoogleURLTrackerInfoBarDelegate*>(infobar->delegate())); 356 } else { 357 map_entry->Close(false); 358 } 359 } 360 } 361 362 void GoogleURLTracker::OnTabClosed( 363 content::NavigationController* navigation_controller) { 364 // Because InfoBarService tears itself down on tab destruction, it's possible 365 // to get a non-NULL InfoBarService pointer here, depending on which order 366 // notifications fired in. Likewise, the pointer in |entry_map_| (and in its 367 // associated MapEntry) may point to deleted memory. Therefore, if we were to 368 // access the InfoBarService* we have for this tab, we'd need to ensure we 369 // just looked at the raw pointer value, and never dereferenced it. This 370 // function doesn't need to do even that, but others in the call chain from 371 // here might (and have comments pointing back here). 372 for (EntryMap::iterator i(entry_map_.begin()); i != entry_map_.end(); ++i) { 373 if (i->second->navigation_controller() == navigation_controller) { 374 i->second->Close(false); 375 return; 376 } 377 } 378 NOTREACHED(); 379 } 380 381 void GoogleURLTracker::CloseAllEntries(bool redo_searches) { 382 // Delete all entries, whether they have infobars or not. 383 while (!entry_map_.empty()) 384 entry_map_.begin()->second->Close(redo_searches); 385 } 386 387 void GoogleURLTracker::UnregisterForEntrySpecificNotifications( 388 const GoogleURLTrackerMapEntry& map_entry, 389 bool must_be_listening_for_commit) { 390 // For tabs with map entries but no infobars, we should always be listening 391 // for both these notifications. For tabs with infobars, we may be listening 392 // for navigation commits if the user has performed a new search on this tab. 393 if (nav_helper_->IsListeningForNavigationCommit( 394 map_entry.navigation_controller())) { 395 nav_helper_->SetListeningForNavigationCommit( 396 map_entry.navigation_controller(), false); 397 } else { 398 DCHECK(!must_be_listening_for_commit); 399 DCHECK(map_entry.has_infobar_delegate()); 400 } 401 const bool registered_for_tab_destruction = 402 nav_helper_->IsListeningForTabDestruction( 403 map_entry.navigation_controller()); 404 DCHECK_NE(registered_for_tab_destruction, map_entry.has_infobar_delegate()); 405 if (registered_for_tab_destruction) { 406 nav_helper_->SetListeningForTabDestruction( 407 map_entry.navigation_controller(), false); 408 } 409 410 // Our global listeners for these other notifications should be in place iff 411 // we have any tabs still listening for commits. These tabs either have no 412 // infobars or have received new pending searches atop existing infobars; in 413 // either case we want to catch subsequent pending non-search navigations. 414 // See the various cases inside OnNavigationPending(). 415 for (EntryMap::const_iterator i(entry_map_.begin()); i != entry_map_.end(); 416 ++i) { 417 if (nav_helper_->IsListeningForNavigationCommit( 418 i->second->navigation_controller())) { 419 DCHECK(nav_helper_->IsListeningForNavigationStart()); 420 return; 421 } 422 } 423 if (nav_helper_->IsListeningForNavigationStart()) { 424 DCHECK(!search_committed_); 425 nav_helper_->SetListeningForNavigationStart(false); 426 } 427 } 428