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.
5 #include "components/google/core/browser/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 "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"
24 const char GoogleURLTracker::kDefaultGoogleHomepage
[] =
25 "http://www.google.com/";
26 const char GoogleURLTracker::kSearchDomainCheckURL
[] =
27 "https://www.google.com/searchdomaincheck?format=url&type=chrome";
29 GoogleURLTracker::GoogleURLTracker(scoped_ptr
<GoogleURLTrackerClient
> client
,
31 : client_(client
.Pass()),
32 google_url_(mode
== UNIT_TEST_MODE
?
33 kDefaultGoogleHomepage
:
34 client_
->GetPrefs()->GetString(prefs::kLastKnownGoogleURL
)),
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::AddIPAddressObserver(this);
43 client_
->set_google_url_tracker(this);
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.
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
));
63 GoogleURLTracker::~GoogleURLTracker() {
64 // We should only reach here after any tabs and their infobars have been torn
66 DCHECK(entry_map_
.empty());
69 void GoogleURLTracker::RequestServerCheck(bool force
) {
70 // If this instance already has a fetcher, SetNeedToFetch() is unnecessary,
71 // and changing |already_fetched_| is wrong.
74 already_fetched_
= false;
79 void 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
84 if (!client_
->IsListeningForNavigationStart())
85 client_
->SetListeningForNavigationStart(true);
89 void 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();
97 need_to_prompt_
= false;
98 CloseAllEntries(redo_searches
);
101 void GoogleURLTracker::CancelGoogleURL() {
102 client_
->GetPrefs()->SetString(prefs::kLastPromptedGoogleURL
,
103 fetched_google_url_
.spec());
104 need_to_prompt_
= false;
105 CloseAllEntries(false);
108 void 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());
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;
118 // See if the response data was valid. It should be
119 // "<scheme>://[www.]google.<TLD>/".
121 source
->GetResponseAsString(&url_str
);
122 base::TrimWhitespace(url_str
, base::TRIM_ALL
, &url_str
);
124 if (!url
.is_valid() || (url
.path().length() > 1) || url
.has_query() ||
126 !google_util::IsGoogleDomainUrl(url
,
127 google_util::DISALLOW_SUBDOMAIN
,
128 google_util::DISALLOW_NON_STANDARD_PORTS
))
131 std::swap(url
, fetched_google_url_
);
132 GURL
last_prompted_url(
133 client_
->GetPrefs()->GetString(prefs::kLastPromptedGoogleURL
));
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.
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
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.
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;
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);
182 void GoogleURLTracker::OnIPAddressChanged() {
183 already_fetched_
= false;
184 StartFetchIfDesirable();
187 void GoogleURLTracker::Shutdown() {
190 weak_ptr_factory_
.InvalidateWeakPtrs();
191 net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
194 void GoogleURLTracker::DeleteMapEntryForManager(
195 const infobars::InfoBarManager
* infobar_manager
) {
196 // WARNING: |infobar_manager| may point to a deleted object. Do not
197 // dereference it! See OnTabClosed().
198 EntryMap::iterator
i(entry_map_
.find(infobar_manager
));
199 DCHECK(i
!= entry_map_
.end());
200 GoogleURLTrackerMapEntry
* map_entry
= i
->second
;
202 UnregisterForEntrySpecificNotifications(map_entry
, false);
207 void GoogleURLTracker::SetNeedToFetch() {
208 need_to_fetch_
= true;
209 StartFetchIfDesirable();
212 void GoogleURLTracker::FinishSleep() {
213 in_startup_sleep_
= false;
214 StartFetchIfDesirable();
217 void GoogleURLTracker::StartFetchIfDesirable() {
218 // Bail if a fetch isn't appropriate right now. This function will be called
219 // again each time one of the preconditions changes, so we'll fetch
220 // immediately once all of them are met.
222 // See comments in header on the class, on RequestServerCheck(), and on the
223 // various members here for more detail on exactly what the conditions are.
224 if (in_startup_sleep_
|| already_fetched_
|| !need_to_fetch_
)
227 // Some switches should disable the Google URL tracker entirely. If we can't
228 // do background networking, we can't do the necessary fetch, and if the user
229 // specified a Google base URL manually, we shouldn't bother to look up any
230 // alternatives or offer to switch to them.
231 if (!client_
->IsBackgroundNetworkingEnabled() ||
232 CommandLine::ForCurrentProcess()->HasSwitch(switches::kGoogleBaseURL
))
235 already_fetched_
= true;
236 fetcher_
.reset(net::URLFetcher::Create(
237 fetcher_id_
, GURL(kSearchDomainCheckURL
), net::URLFetcher::GET
, this));
239 // We don't want this fetch to set new entries in the cache or cookies, lest
240 // we alarm the user.
241 fetcher_
->SetLoadFlags(net::LOAD_DISABLE_CACHE
|
242 net::LOAD_DO_NOT_SAVE_COOKIES
);
243 fetcher_
->SetRequestContext(client_
->GetRequestContext());
245 // Configure to max_retries at most kMaxRetries times for 5xx errors.
246 static const int kMaxRetries
= 5;
247 fetcher_
->SetMaxRetriesOn5xx(kMaxRetries
);
252 void GoogleURLTracker::OnNavigationPending(
253 scoped_ptr
<GoogleURLTrackerNavigationHelper
> nav_helper
,
254 infobars::InfoBarManager
* infobar_manager
,
256 GoogleURLTrackerMapEntry
* map_entry
= NULL
;
258 EntryMap::iterator
i(entry_map_
.find(infobar_manager
));
259 if (i
!= entry_map_
.end())
260 map_entry
= i
->second
;
262 if (search_committed_
) {
263 search_committed_
= false;
265 // This is a search on a tab that doesn't have one of our infobars, so
266 // prepare to add one. Note that we only listen for the tab's destruction
267 // on this path; if there was already a map entry, then either it doesn't
268 // yet have an infobar and we're already registered for this, or it has an
269 // infobar and the infobar's owner will handle tearing it down when the
271 map_entry
= new GoogleURLTrackerMapEntry(
272 this, infobar_manager
, nav_helper
.Pass());
273 map_entry
->navigation_helper()->SetListeningForTabDestruction(true);
274 entry_map_
.insert(std::make_pair(infobar_manager
, map_entry
));
275 } else if (map_entry
->infobar_delegate()) {
276 // This is a new search on a tab where we already have an infobar.
277 map_entry
->infobar_delegate()->set_pending_id(pending_id
);
280 // Whether there's an existing infobar or not, we need to listen for the
281 // load to commit, so we can show and/or update the infobar when it does.
282 // (We may already be registered for this if there is an existing infobar
283 // that had a previous pending search that hasn't yet committed.)
284 if (!map_entry
->navigation_helper()->IsListeningForNavigationCommit())
285 map_entry
->navigation_helper()->SetListeningForNavigationCommit(true);
286 } else if (map_entry
) {
287 if (map_entry
->has_infobar_delegate()) {
288 // This is a non-search navigation on a tab with an infobar. If there was
289 // a previous pending search on this tab, this means it won't commit, so
290 // undo anything we did in response to seeing that. Note that if there
291 // was no pending search on this tab, these statements are effectively a
294 // If this navigation actually commits, that will trigger the infobar's
295 // owner to expire the infobar if need be. If it doesn't commit, then
296 // simply leaving the infobar as-is will have been the right thing.
297 UnregisterForEntrySpecificNotifications(map_entry
, false);
298 map_entry
->infobar_delegate()->set_pending_id(0);
300 // Non-search navigation on a tab with an entry that has not yet created
301 // an infobar. This means the original search won't commit, so delete the
303 map_entry
->Close(false);
306 // Non-search navigation on a tab without an infobars. This is irrelevant
311 void GoogleURLTracker::OnNavigationCommitted(
312 infobars::InfoBarManager
* infobar_manager
,
313 const GURL
& search_url
) {
314 EntryMap::iterator
i(entry_map_
.find(infobar_manager
));
315 DCHECK(i
!= entry_map_
.end());
316 GoogleURLTrackerMapEntry
* map_entry
= i
->second
;
317 DCHECK(search_url
.is_valid());
319 UnregisterForEntrySpecificNotifications(map_entry
, true);
320 if (map_entry
->has_infobar_delegate()) {
321 map_entry
->infobar_delegate()->Update(search_url
);
323 infobars::InfoBar
* infobar
= GoogleURLTrackerInfoBarDelegate::Create(
324 infobar_manager
, this, search_url
);
326 map_entry
->SetInfoBarDelegate(
327 static_cast<GoogleURLTrackerInfoBarDelegate
*>(infobar
->delegate()));
329 map_entry
->Close(false);
334 void GoogleURLTracker::OnTabClosed(
335 GoogleURLTrackerNavigationHelper
* nav_helper
) {
336 // Because InfoBarManager tears itself down on tab destruction, it's possible
337 // to get a non-NULL InfoBarManager pointer here, depending on which order
338 // notifications fired in. Likewise, the pointer in |entry_map_| (and in its
339 // associated MapEntry) may point to deleted memory. Therefore, if we were
340 // to access the InfoBarManager* we have for this tab, we'd need to ensure we
341 // just looked at the raw pointer value, and never dereferenced it. This
342 // function doesn't need to do even that, but others in the call chain from
343 // here might (and have comments pointing back here).
344 for (EntryMap::iterator
i(entry_map_
.begin()); i
!= entry_map_
.end(); ++i
) {
345 if (i
->second
->navigation_helper() == nav_helper
) {
346 i
->second
->Close(false);
353 scoped_ptr
<GoogleURLTracker::Subscription
> GoogleURLTracker::RegisterCallback(
354 const OnGoogleURLUpdatedCallback
& cb
) {
355 return callback_list_
.Add(cb
);
358 void GoogleURLTracker::CloseAllEntries(bool redo_searches
) {
359 // Delete all entries, whether they have infobars or not.
360 while (!entry_map_
.empty())
361 entry_map_
.begin()->second
->Close(redo_searches
);
364 void GoogleURLTracker::UnregisterForEntrySpecificNotifications(
365 GoogleURLTrackerMapEntry
* map_entry
,
366 bool must_be_listening_for_commit
) {
367 // For tabs with map entries but no infobars, we should always be listening
368 // for both these notifications. For tabs with infobars, we may be listening
369 // for navigation commits if the user has performed a new search on this tab.
370 if (map_entry
->navigation_helper()->IsListeningForNavigationCommit()) {
371 map_entry
->navigation_helper()->SetListeningForNavigationCommit(false);
373 DCHECK(!must_be_listening_for_commit
);
374 DCHECK(map_entry
->has_infobar_delegate());
376 const bool registered_for_tab_destruction
=
377 map_entry
->navigation_helper()->IsListeningForTabDestruction();
378 DCHECK_NE(registered_for_tab_destruction
, map_entry
->has_infobar_delegate());
379 if (registered_for_tab_destruction
) {
380 map_entry
->navigation_helper()->SetListeningForTabDestruction(false);
383 // Our global listeners for these other notifications should be in place iff
384 // we have any tabs still listening for commits. These tabs either have no
385 // infobars or have received new pending searches atop existing infobars; in
386 // either case we want to catch subsequent pending non-search navigations.
387 // See the various cases inside OnNavigationPending().
388 for (EntryMap::const_iterator
i(entry_map_
.begin()); i
!= entry_map_
.end();
390 if (i
->second
->navigation_helper()->IsListeningForNavigationCommit()) {
391 DCHECK(client_
->IsListeningForNavigationStart());
395 if (client_
->IsListeningForNavigationStart()) {
396 DCHECK(!search_committed_
);
397 client_
->SetListeningForNavigationStart(false);
401 void GoogleURLTracker::NotifyGoogleURLUpdated() {
402 callback_list_
.Notify();