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"
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_service.h"
17 #include "chrome/browser/profiles/profile.h"
18 #include "chrome/common/chrome_switches.h"
19 #include "chrome/common/pref_names.h"
20 #include "components/google/core/browser/google_url_tracker_client.h"
21 #include "components/infobars/core/infobar.h"
22 #include "content/public/browser/navigation_controller.h"
23 #include "content/public/browser/navigation_entry.h"
24 #include "content/public/browser/notification_service.h"
25 #include "net/base/load_flags.h"
26 #include "net/base/net_util.h"
27 #include "net/url_request/url_fetcher.h"
28 #include "net/url_request/url_request_status.h"
31 const char GoogleURLTracker::kDefaultGoogleHomepage
[] =
32 "http://www.google.com/";
33 const char GoogleURLTracker::kSearchDomainCheckURL
[] =
34 "https://www.google.com/searchdomaincheck?format=url&type=chrome";
36 GoogleURLTracker::GoogleURLTracker(
38 scoped_ptr
<GoogleURLTrackerClient
> client
,
39 scoped_ptr
<GoogleURLTrackerNavigationHelper
> nav_helper
,
42 client_(client
.Pass()),
43 nav_helper_(nav_helper
.Pass()),
44 infobar_creator_(base::Bind(&GoogleURLTrackerInfoBarDelegate::Create
)),
45 google_url_(mode
== UNIT_TEST_MODE
?
46 kDefaultGoogleHomepage
:
47 profile
->GetPrefs()->GetString(prefs::kLastKnownGoogleURL
)),
49 in_startup_sleep_(true),
50 already_fetched_(false),
51 need_to_fetch_(false),
52 need_to_prompt_(false),
53 search_committed_(false),
54 weak_ptr_factory_(this) {
55 net::NetworkChangeNotifier::AddIPAddressObserver(this);
56 client_
->set_google_url_tracker(this);
57 nav_helper_
->SetGoogleURLTracker(this);
59 // Because this function can be called during startup, when kicking off a URL
60 // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully
61 // long enough to be after startup, but still get results back quickly.
62 // Ideally, instead of this timer, we'd do something like "check if the
63 // browser is starting up, and if so, come back later", but there is currently
64 // no function to do this.
66 // In UNIT_TEST mode, where we want to explicitly control when the tracker
67 // "wakes up", we do nothing at all.
68 if (mode
== NORMAL_MODE
) {
69 static const int kStartFetchDelayMS
= 5000;
70 base::MessageLoop::current()->PostDelayedTask(FROM_HERE
,
71 base::Bind(&GoogleURLTracker::FinishSleep
,
72 weak_ptr_factory_
.GetWeakPtr()),
73 base::TimeDelta::FromMilliseconds(kStartFetchDelayMS
));
77 GoogleURLTracker::~GoogleURLTracker() {
78 // We should only reach here after any tabs and their infobars have been torn
80 DCHECK(entry_map_
.empty());
84 GURL
GoogleURLTracker::GoogleURL(Profile
* profile
) {
85 const GoogleURLTracker
* tracker
=
86 GoogleURLTrackerFactory::GetForProfile(profile
);
87 return tracker
? tracker
->google_url_
: GURL(kDefaultGoogleHomepage
);
91 void GoogleURLTracker::RequestServerCheck(Profile
* profile
, bool force
) {
92 GoogleURLTracker
* tracker
= GoogleURLTrackerFactory::GetForProfile(profile
);
93 // If the tracker already has a fetcher, SetNeedToFetch() is unnecessary, and
94 // changing |already_fetched_| is wrong.
95 if (tracker
&& !tracker
->fetcher_
) {
97 tracker
->already_fetched_
= false;
98 tracker
->SetNeedToFetch();
103 void GoogleURLTracker::GoogleURLSearchCommitted(Profile
* profile
) {
104 GoogleURLTracker
* tracker
= GoogleURLTrackerFactory::GetForProfile(profile
);
106 tracker
->SearchCommitted();
109 void GoogleURLTracker::AcceptGoogleURL(bool redo_searches
) {
110 GURL old_google_url
= google_url_
;
111 google_url_
= fetched_google_url_
;
112 PrefService
* prefs
= profile_
->GetPrefs();
113 prefs
->SetString(prefs::kLastKnownGoogleURL
, google_url_
.spec());
114 prefs
->SetString(prefs::kLastPromptedGoogleURL
, google_url_
.spec());
115 NotifyGoogleURLUpdated(old_google_url
, google_url_
);
117 need_to_prompt_
= false;
118 CloseAllEntries(redo_searches
);
121 void GoogleURLTracker::CancelGoogleURL() {
122 profile_
->GetPrefs()->SetString(prefs::kLastPromptedGoogleURL
,
123 fetched_google_url_
.spec());
124 need_to_prompt_
= false;
125 CloseAllEntries(false);
128 void GoogleURLTracker::OnURLFetchComplete(const net::URLFetcher
* source
) {
129 // Delete the fetcher on this function's exit.
130 scoped_ptr
<net::URLFetcher
> clean_up_fetcher(fetcher_
.release());
132 // Don't update the URL if the request didn't succeed.
133 if (!source
->GetStatus().is_success() || (source
->GetResponseCode() != 200)) {
134 already_fetched_
= false;
138 // See if the response data was valid. It should be
139 // "<scheme>://[www.]google.<TLD>/".
141 source
->GetResponseAsString(&url_str
);
142 base::TrimWhitespace(url_str
, base::TRIM_ALL
, &url_str
);
144 if (!url
.is_valid() || (url
.path().length() > 1) || url
.has_query() ||
146 !google_util::IsGoogleDomainUrl(url
, google_util::DISALLOW_SUBDOMAIN
,
147 google_util::DISALLOW_NON_STANDARD_PORTS
))
150 std::swap(url
, fetched_google_url_
);
151 GURL
last_prompted_url(
152 profile_
->GetPrefs()->GetString(prefs::kLastPromptedGoogleURL
));
154 if (last_prompted_url
.is_empty()) {
155 // On the very first run of Chrome, when we've never looked up the URL at
156 // all, we should just silently switch over to whatever we get immediately.
157 AcceptGoogleURL(true); // Arg is irrelevant.
161 base::string16
fetched_host(net::StripWWWFromHost(fetched_google_url_
));
162 if (fetched_google_url_
== google_url_
) {
163 // Either the user has continually been on this URL, or we prompted for a
164 // different URL but have now changed back before they responded to any of
165 // the prompts. In this latter case we want to close any infobars and stop
168 } else if (fetched_host
== net::StripWWWFromHost(google_url_
)) {
169 // Similar to the above case, but this time the new URL differs from the
170 // existing one, probably due to switching between HTTP and HTTPS searching.
171 // Like before we want to close any infobars and stop prompting; we also
172 // want to silently accept the change in scheme. We don't redo open
173 // searches so as to avoid suddenly changing a page the user might be
174 // interacting with; it's enough to simply get future searches right.
175 AcceptGoogleURL(false);
176 } else if (fetched_host
== net::StripWWWFromHost(last_prompted_url
)) {
177 // We've re-fetched a TLD the user previously turned down. Although the new
178 // URL might have a different scheme than the old, we want to preserve the
179 // user's decision. Note that it's possible that, like in the above two
180 // cases, we fetched yet another different URL in the meantime, which we
181 // have infobars prompting about; in this case, as in those above, we want
182 // to go ahead and close the infobars and stop prompting, since we've
183 // switched back away from that URL.
186 // We've fetched a URL with a different TLD than the user is currently using
187 // or was previously prompted about. This means we need to prompt again.
188 need_to_prompt_
= true;
190 // As in all the above cases, there could be infobars prompting about some
191 // URL. If these URLs have the same TLD (e.g. for scheme changes), we can
192 // simply leave the existing infobars open as their messages will still be
193 // accurate. Otherwise we go ahead and close them because we need to
194 // display a new message.
195 // Note: |url| is the previous |fetched_google_url_|.
196 if (url
.is_valid() && (fetched_host
!= net::StripWWWFromHost(url
)))
197 CloseAllEntries(false);
201 void GoogleURLTracker::OnIPAddressChanged() {
202 already_fetched_
= false;
203 StartFetchIfDesirable();
206 void GoogleURLTracker::Shutdown() {
210 weak_ptr_factory_
.InvalidateWeakPtrs();
211 net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
214 void GoogleURLTracker::DeleteMapEntryForService(
215 const InfoBarService
* infobar_service
) {
216 // WARNING: |infobar_service| may point to a deleted object. Do not
217 // dereference it! See OnTabClosed().
218 EntryMap::iterator
i(entry_map_
.find(infobar_service
));
219 DCHECK(i
!= entry_map_
.end());
220 GoogleURLTrackerMapEntry
* map_entry
= i
->second
;
222 UnregisterForEntrySpecificNotifications(*map_entry
, false);
227 void GoogleURLTracker::SetNeedToFetch() {
228 need_to_fetch_
= true;
229 StartFetchIfDesirable();
232 void GoogleURLTracker::FinishSleep() {
233 in_startup_sleep_
= false;
234 StartFetchIfDesirable();
237 void GoogleURLTracker::StartFetchIfDesirable() {
238 // Bail if a fetch isn't appropriate right now. This function will be called
239 // again each time one of the preconditions changes, so we'll fetch
240 // immediately once all of them are met.
242 // See comments in header on the class, on RequestServerCheck(), and on the
243 // various members here for more detail on exactly what the conditions are.
244 if (in_startup_sleep_
|| already_fetched_
|| !need_to_fetch_
)
247 // Some switches should disable the Google URL tracker entirely. If we can't
248 // do background networking, we can't do the necessary fetch, and if the user
249 // specified a Google base URL manually, we shouldn't bother to look up any
250 // alternatives or offer to switch to them.
251 if (CommandLine::ForCurrentProcess()->HasSwitch(
252 switches::kDisableBackgroundNetworking
) ||
253 CommandLine::ForCurrentProcess()->HasSwitch(switches::kGoogleBaseURL
))
256 already_fetched_
= true;
257 fetcher_
.reset(net::URLFetcher::Create(
258 fetcher_id_
, GURL(kSearchDomainCheckURL
), net::URLFetcher::GET
, this));
260 // We don't want this fetch to set new entries in the cache or cookies, lest
261 // we alarm the user.
262 fetcher_
->SetLoadFlags(net::LOAD_DISABLE_CACHE
|
263 net::LOAD_DO_NOT_SAVE_COOKIES
);
264 fetcher_
->SetRequestContext(profile_
->GetRequestContext());
266 // Configure to max_retries at most kMaxRetries times for 5xx errors.
267 static const int kMaxRetries
= 5;
268 fetcher_
->SetMaxRetriesOn5xx(kMaxRetries
);
273 void GoogleURLTracker::SearchCommitted() {
274 if (need_to_prompt_
) {
275 search_committed_
= true;
276 // These notifications will fire a bit later in the same call chain we're
278 if (!client_
->IsListeningForNavigationStart())
279 client_
->SetListeningForNavigationStart(true);
283 void GoogleURLTracker::OnNavigationPending(
284 content::NavigationController
* navigation_controller
,
285 InfoBarService
* infobar_service
,
287 EntryMap::iterator
i(entry_map_
.find(infobar_service
));
289 if (search_committed_
) {
290 search_committed_
= false;
291 // Whether there's an existing infobar or not, we need to listen for the
292 // load to commit, so we can show and/or update the infobar when it does.
293 // (We may already be registered for this if there is an existing infobar
294 // that had a previous pending search that hasn't yet committed.)
295 if (!nav_helper_
->IsListeningForNavigationCommit(navigation_controller
)) {
296 nav_helper_
->SetListeningForNavigationCommit(navigation_controller
,
299 if (i
== entry_map_
.end()) {
300 // This is a search on a tab that doesn't have one of our infobars, so
301 // prepare to add one. Note that we only listen for the tab's destruction
302 // on this path; if there was already a map entry, then either it doesn't
303 // yet have an infobar and we're already registered for this, or it has an
304 // infobar and the infobar's owner will handle tearing it down when the
306 nav_helper_
->SetListeningForTabDestruction(navigation_controller
, true);
307 entry_map_
.insert(std::make_pair(
309 new GoogleURLTrackerMapEntry(this, infobar_service
,
310 navigation_controller
)));
311 } else if (i
->second
->has_infobar_delegate()) {
312 // This is a new search on a tab where we already have an infobar.
313 i
->second
->infobar_delegate()->set_pending_id(pending_id
);
315 } else if (i
!= entry_map_
.end()){
316 if (i
->second
->has_infobar_delegate()) {
317 // This is a non-search navigation on a tab with an infobar. If there was
318 // a previous pending search on this tab, this means it won't commit, so
319 // undo anything we did in response to seeing that. Note that if there
320 // was no pending search on this tab, these statements are effectively a
323 // If this navigation actually commits, that will trigger the infobar's
324 // owner to expire the infobar if need be. If it doesn't commit, then
325 // simply leaving the infobar as-is will have been the right thing.
326 UnregisterForEntrySpecificNotifications(*i
->second
, false);
327 i
->second
->infobar_delegate()->set_pending_id(0);
329 // Non-search navigation on a tab with an entry that has not yet created
330 // an infobar. This means the original search won't commit, so delete the
332 i
->second
->Close(false);
335 // Non-search navigation on a tab without an infobars. This is irrelevant
340 void GoogleURLTracker::OnNavigationCommitted(InfoBarService
* infobar_service
,
341 const GURL
& search_url
) {
342 EntryMap::iterator
i(entry_map_
.find(infobar_service
));
343 DCHECK(i
!= entry_map_
.end());
344 GoogleURLTrackerMapEntry
* map_entry
= i
->second
;
345 DCHECK(search_url
.is_valid());
347 UnregisterForEntrySpecificNotifications(*map_entry
, true);
348 if (map_entry
->has_infobar_delegate()) {
349 map_entry
->infobar_delegate()->Update(search_url
);
351 infobars::InfoBar
* infobar
=
352 infobar_creator_
.Run(infobar_service
, this, search_url
);
354 map_entry
->SetInfoBarDelegate(
355 static_cast<GoogleURLTrackerInfoBarDelegate
*>(infobar
->delegate()));
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);
381 scoped_ptr
<GoogleURLTracker::Subscription
> GoogleURLTracker::RegisterCallback(
382 const OnGoogleURLUpdatedCallback
& cb
) {
383 return callback_list_
.Add(cb
);
386 void GoogleURLTracker::CloseAllEntries(bool redo_searches
) {
387 // Delete all entries, whether they have infobars or not.
388 while (!entry_map_
.empty())
389 entry_map_
.begin()->second
->Close(redo_searches
);
392 void GoogleURLTracker::UnregisterForEntrySpecificNotifications(
393 const GoogleURLTrackerMapEntry
& map_entry
,
394 bool must_be_listening_for_commit
) {
395 // For tabs with map entries but no infobars, we should always be listening
396 // for both these notifications. For tabs with infobars, we may be listening
397 // for navigation commits if the user has performed a new search on this tab.
398 if (nav_helper_
->IsListeningForNavigationCommit(
399 map_entry
.navigation_controller())) {
400 nav_helper_
->SetListeningForNavigationCommit(
401 map_entry
.navigation_controller(), false);
403 DCHECK(!must_be_listening_for_commit
);
404 DCHECK(map_entry
.has_infobar_delegate());
406 const bool registered_for_tab_destruction
=
407 nav_helper_
->IsListeningForTabDestruction(
408 map_entry
.navigation_controller());
409 DCHECK_NE(registered_for_tab_destruction
, map_entry
.has_infobar_delegate());
410 if (registered_for_tab_destruction
) {
411 nav_helper_
->SetListeningForTabDestruction(
412 map_entry
.navigation_controller(), false);
415 // Our global listeners for these other notifications should be in place iff
416 // we have any tabs still listening for commits. These tabs either have no
417 // infobars or have received new pending searches atop existing infobars; in
418 // either case we want to catch subsequent pending non-search navigations.
419 // See the various cases inside OnNavigationPending().
420 for (EntryMap::const_iterator
i(entry_map_
.begin()); i
!= entry_map_
.end();
422 if (nav_helper_
->IsListeningForNavigationCommit(
423 i
->second
->navigation_controller())) {
424 DCHECK(client_
->IsListeningForNavigationStart());
428 if (client_
->IsListeningForNavigationStart()) {
429 DCHECK(!search_committed_
);
430 client_
->SetListeningForNavigationStart(false);
434 void GoogleURLTracker::NotifyGoogleURLUpdated(GURL old_url
, GURL new_url
) {
435 callback_list_
.Notify(old_url
, new_url
);