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