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