NaCl: Update revision in DEPS, r12770 -> r12773
[chromium-blink-merge.git] / chrome / browser / google / google_url_tracker.cc
blobd971caf210255a65f84795145fda9abf3db2f856
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.
5 #include "chrome/browser/google/google_url_tracker.h"
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"
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";
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 fetcher_id_(0),
45 in_startup_sleep_(true),
46 already_fetched_(false),
47 need_to_fetch_(false),
48 need_to_prompt_(false),
49 search_committed_(false),
50 weak_ptr_factory_(this) {
51 net::NetworkChangeNotifier::AddIPAddressObserver(this);
52 nav_helper_->SetGoogleURLTracker(this);
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.
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));
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());
78 // static
79 GURL GoogleURLTracker::GoogleURL(Profile* profile) {
80 const GoogleURLTracker* tracker =
81 GoogleURLTrackerFactory::GetForProfile(profile);
82 return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage);
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();
97 // static
98 void GoogleURLTracker::GoogleURLSearchCommitted(Profile* profile) {
99 GoogleURLTracker* tracker = GoogleURLTrackerFactory::GetForProfile(profile);
100 if (tracker)
101 tracker->SearchCommitted();
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);
118 void GoogleURLTracker::CancelGoogleURL() {
119 profile_->GetPrefs()->SetString(prefs::kLastPromptedGoogleURL,
120 fetched_google_url_.spec());
121 need_to_prompt_ = false;
122 CloseAllEntries(false);
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());
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;
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;
147 std::swap(url, fetched_google_url_);
148 GURL last_prompted_url(
149 profile_->GetPrefs()->GetString(prefs::kLastPromptedGoogleURL));
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;
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;
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);
198 void GoogleURLTracker::OnIPAddressChanged() {
199 already_fetched_ = false;
200 StartFetchIfDesirable();
203 void GoogleURLTracker::Shutdown() {
204 nav_helper_.reset();
205 fetcher_.reset();
206 weak_ptr_factory_.InvalidateWeakPtrs();
207 net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
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;
218 UnregisterForEntrySpecificNotifications(*map_entry, false);
219 entry_map_.erase(i);
220 delete map_entry;
223 void GoogleURLTracker::SetNeedToFetch() {
224 need_to_fetch_ = true;
225 StartFetchIfDesirable();
228 void GoogleURLTracker::FinishSleep() {
229 in_startup_sleep_ = false;
230 StartFetchIfDesirable();
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.
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;
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;
252 std::string fetch_url = CommandLine::ForCurrentProcess()->
253 GetSwitchValueASCII(switches::kGoogleSearchDomainCheckURL);
254 if (fetch_url.empty())
255 fetch_url = kSearchDomainCheckURL;
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());
267 // Configure to max_retries at most kMaxRetries times for 5xx errors.
268 static const int kMaxRetries = 5;
269 fetcher_->SetMaxRetriesOn5xx(kMaxRetries);
271 fetcher_->Start();
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);
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));
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);
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);
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.
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);
335 } else {
336 // Non-search navigation on a tab without an infobars. This is irrelevant
337 // to us.
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());
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);
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;
378 NOTREACHED();
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);
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());
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);
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;
423 if (nav_helper_->IsListeningForNavigationStart()) {
424 DCHECK(!search_committed_);
425 nav_helper_->SetListeningForNavigationStart(false);