Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(179)

Side by Side Diff: chrome/browser/google/google_url_tracker.cc

Issue 316203003: Componentize GoogleURLTracker(InfoBarDelegate,MapEntry,NavHelper) (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Nits Created 6 years, 6 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(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 }
OLDNEW
« no previous file with comments | « chrome/browser/google/google_url_tracker.h ('k') | chrome/browser/google/google_url_tracker_factory.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698