- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / google / google_url_tracker.cc
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/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 "content/public/browser/navigation_controller.h"
21 #include "content/public/browser/navigation_entry.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(
35     Profile* profile,
36     scoped_ptr<GoogleURLTrackerNavigationHelper> nav_helper,
37     Mode mode)
38     : profile_(profile),
39       nav_helper_(nav_helper.Pass()),
40       infobar_creator_(base::Bind(&GoogleURLTrackerInfoBarDelegate::Create)),
41       google_url_(mode == UNIT_TEST_MODE ? kDefaultGoogleHomepage :
42           profile->GetPrefs()->GetString(prefs::kLastKnownGoogleURL)),
43       weak_ptr_factory_(this),
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   net::NetworkChangeNotifier::AddIPAddressObserver(this);
51   nav_helper_->SetGoogleURLTracker(this);
52
53   // Because this function can be called during startup, when kicking off a URL
54   // fetch can eat up 20 ms of time, we delay five seconds, which is hopefully
55   // long enough to be after startup, but still get results back quickly.
56   // Ideally, instead of this timer, we'd do something like "check if the
57   // browser is starting up, and if so, come back later", but there is currently
58   // no function to do this.
59   //
60   // In UNIT_TEST mode, where we want to explicitly control when the tracker
61   // "wakes up", we do nothing at all.
62   if (mode == NORMAL_MODE) {
63     static const int kStartFetchDelayMS = 5000;
64     base::MessageLoop::current()->PostDelayedTask(FROM_HERE,
65         base::Bind(&GoogleURLTracker::FinishSleep,
66                    weak_ptr_factory_.GetWeakPtr()),
67         base::TimeDelta::FromMilliseconds(kStartFetchDelayMS));
68   }
69 }
70
71 GoogleURLTracker::~GoogleURLTracker() {
72   // We should only reach here after any tabs and their infobars have been torn
73   // down.
74   DCHECK(entry_map_.empty());
75 }
76
77 // static
78 GURL GoogleURLTracker::GoogleURL(Profile* profile) {
79   const GoogleURLTracker* tracker =
80       GoogleURLTrackerFactory::GetForProfile(profile);
81   return tracker ? tracker->google_url_ : GURL(kDefaultGoogleHomepage);
82 }
83
84 // static
85 void GoogleURLTracker::RequestServerCheck(Profile* profile, bool force) {
86   GoogleURLTracker* tracker = GoogleURLTrackerFactory::GetForProfile(profile);
87   // If the tracker already has a fetcher, SetNeedToFetch() is unnecessary, and
88   // changing |already_fetched_| is wrong.
89   if (tracker && !tracker->fetcher_) {
90     if (force)
91       tracker->already_fetched_ = false;
92     tracker->SetNeedToFetch();
93   }
94 }
95
96 // static
97 void GoogleURLTracker::GoogleURLSearchCommitted(Profile* profile) {
98   GoogleURLTracker* tracker = GoogleURLTrackerFactory::GetForProfile(profile);
99   if (tracker)
100     tracker->SearchCommitted();
101 }
102
103 void GoogleURLTracker::AcceptGoogleURL(bool redo_searches) {
104   UpdatedDetails urls(google_url_, fetched_google_url_);
105   google_url_ = fetched_google_url_;
106   PrefService* prefs = profile_->GetPrefs();
107   prefs->SetString(prefs::kLastKnownGoogleURL, google_url_.spec());
108   prefs->SetString(prefs::kLastPromptedGoogleURL, google_url_.spec());
109   content::NotificationService::current()->Notify(
110       chrome::NOTIFICATION_GOOGLE_URL_UPDATED,
111       content::Source<Profile>(profile_),
112       content::Details<UpdatedDetails>(&urls));
113   need_to_prompt_ = false;
114   CloseAllEntries(redo_searches);
115 }
116
117 void GoogleURLTracker::CancelGoogleURL() {
118   profile_->GetPrefs()->SetString(prefs::kLastPromptedGoogleURL,
119                                   fetched_google_url_.spec());
120   need_to_prompt_ = false;
121   CloseAllEntries(false);
122 }
123
124 void GoogleURLTracker::OnURLFetchComplete(const net::URLFetcher* source) {
125   // Delete the fetcher on this function's exit.
126   scoped_ptr<net::URLFetcher> clean_up_fetcher(fetcher_.release());
127
128   // Don't update the URL if the request didn't succeed.
129   if (!source->GetStatus().is_success() || (source->GetResponseCode() != 200)) {
130     already_fetched_ = false;
131     return;
132   }
133
134   // See if the response data was valid.  It should be
135   // "<scheme>://[www.]google.<TLD>/".
136   std::string url_str;
137   source->GetResponseAsString(&url_str);
138   TrimWhitespace(url_str, TRIM_ALL, &url_str);
139   GURL url(url_str);
140   if (!url.is_valid() || (url.path().length() > 1) || url.has_query() ||
141       url.has_ref() ||
142       !google_util::IsGoogleDomainUrl(url, google_util::DISALLOW_SUBDOMAIN,
143                                       google_util::DISALLOW_NON_STANDARD_PORTS))
144     return;
145
146   std::swap(url, fetched_google_url_);
147   GURL last_prompted_url(
148       profile_->GetPrefs()->GetString(prefs::kLastPromptedGoogleURL));
149
150   if (last_prompted_url.is_empty()) {
151     // On the very first run of Chrome, when we've never looked up the URL at
152     // all, we should just silently switch over to whatever we get immediately.
153     AcceptGoogleURL(true);  // Arg is irrelevant.
154     return;
155   }
156
157   string16 fetched_host(net::StripWWWFromHost(fetched_google_url_));
158   if (fetched_google_url_ == google_url_) {
159     // Either the user has continually been on this URL, or we prompted for a
160     // different URL but have now changed back before they responded to any of
161     // the prompts.  In this latter case we want to close any infobars and stop
162     // prompting.
163     CancelGoogleURL();
164   } else if (fetched_host == net::StripWWWFromHost(google_url_)) {
165     // Similar to the above case, but this time the new URL differs from the
166     // existing one, probably due to switching between HTTP and HTTPS searching.
167     // Like before we want to close any infobars and stop prompting; we also
168     // want to silently accept the change in scheme.  We don't redo open
169     // searches so as to avoid suddenly changing a page the user might be
170     // interacting with; it's enough to simply get future searches right.
171     AcceptGoogleURL(false);
172   } else if (fetched_host == net::StripWWWFromHost(last_prompted_url)) {
173     // We've re-fetched a TLD the user previously turned down.  Although the new
174     // URL might have a different scheme than the old, we want to preserve the
175     // user's decision.  Note that it's possible that, like in the above two
176     // cases, we fetched yet another different URL in the meantime, which we
177     // have infobars prompting about; in this case, as in those above, we want
178     // to go ahead and close the infobars and stop prompting, since we've
179     // switched back away from that URL.
180     CancelGoogleURL();
181   } else {
182     // We've fetched a URL with a different TLD than the user is currently using
183     // or was previously prompted about.  This means we need to prompt again.
184     need_to_prompt_ = true;
185
186     // As in all the above cases, there could be infobars prompting about some
187     // URL.  If these URLs have the same TLD (e.g. for scheme changes), we can
188     // simply leave the existing infobars open as their messages will still be
189     // accurate.  Otherwise we go ahead and close them because we need to
190     // display a new message.
191     // Note: |url| is the previous |fetched_google_url_|.
192     if (url.is_valid() && (fetched_host != net::StripWWWFromHost(url)))
193       CloseAllEntries(false);
194   }
195 }
196
197 void GoogleURLTracker::OnIPAddressChanged() {
198   already_fetched_ = false;
199   StartFetchIfDesirable();
200 }
201
202 void GoogleURLTracker::Shutdown() {
203   nav_helper_.reset();
204   weak_ptr_factory_.InvalidateWeakPtrs();
205   fetcher_.reset();
206   net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
207 }
208
209 void GoogleURLTracker::DeleteMapEntryForService(
210     const InfoBarService* infobar_service) {
211   // WARNING: |infobar_service| may point to a deleted object.  Do not
212   // dereference it!  See OnTabClosed().
213   EntryMap::iterator i(entry_map_.find(infobar_service));
214   DCHECK(i != entry_map_.end());
215   GoogleURLTrackerMapEntry* map_entry = i->second;
216
217   UnregisterForEntrySpecificNotifications(*map_entry, false);
218   entry_map_.erase(i);
219   delete map_entry;
220 }
221
222 void GoogleURLTracker::SetNeedToFetch() {
223   need_to_fetch_ = true;
224   StartFetchIfDesirable();
225 }
226
227 void GoogleURLTracker::FinishSleep() {
228   in_startup_sleep_ = false;
229   StartFetchIfDesirable();
230 }
231
232 void GoogleURLTracker::StartFetchIfDesirable() {
233   // Bail if a fetch isn't appropriate right now.  This function will be called
234   // again each time one of the preconditions changes, so we'll fetch
235   // immediately once all of them are met.
236   //
237   // See comments in header on the class, on RequestServerCheck(), and on the
238   // various members here for more detail on exactly what the conditions are.
239   if (in_startup_sleep_ || already_fetched_ || !need_to_fetch_)
240     return;
241
242   // Some switches should disable the Google URL tracker entirely.  If we can't
243   // do background networking, we can't do the necessary fetch, and if the user
244   // specified a Google base URL manually, we shouldn't bother to look up any
245   // alternatives or offer to switch to them.
246   if (CommandLine::ForCurrentProcess()->HasSwitch(
247       switches::kDisableBackgroundNetworking) ||
248       CommandLine::ForCurrentProcess()->HasSwitch(switches::kGoogleBaseURL))
249     return;
250
251   std::string fetch_url = CommandLine::ForCurrentProcess()->
252       GetSwitchValueASCII(switches::kGoogleSearchDomainCheckURL);
253   if (fetch_url.empty())
254     fetch_url = kSearchDomainCheckURL;
255
256   already_fetched_ = true;
257   fetcher_.reset(net::URLFetcher::Create(fetcher_id_, GURL(fetch_url),
258                                          net::URLFetcher::GET, this));
259   ++fetcher_id_;
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());
265
266   // Configure to max_retries at most kMaxRetries times for 5xx errors.
267   static const int kMaxRetries = 5;
268   fetcher_->SetMaxRetriesOn5xx(kMaxRetries);
269
270   fetcher_->Start();
271 }
272
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
277     // currently in.
278     if (!nav_helper_->IsListeningForNavigationStart())
279       nav_helper_->SetListeningForNavigationStart(true);
280   }
281 }
282
283 void GoogleURLTracker::OnNavigationPending(
284     content::NavigationController* navigation_controller,
285     InfoBarService* infobar_service,
286     int pending_id) {
287   EntryMap::iterator i(entry_map_.find(infobar_service));
288
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,
297                                                    true);
298     }
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
305       // tab is destroyed.
306       nav_helper_->SetListeningForTabDestruction(navigation_controller, true);
307       entry_map_.insert(std::make_pair(
308           infobar_service,
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);
314     }
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
321       // no-op.
322       //
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);
328     } else {
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
331       // entry.
332       i->second->Close(false);
333     }
334   } else {
335     // Non-search navigation on a tab without an infobars.  This is irrelevant
336     // to us.
337   }
338 }
339
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());
346
347   UnregisterForEntrySpecificNotifications(*map_entry, true);
348   if (map_entry->has_infobar_delegate()) {
349     map_entry->infobar_delegate()->Update(search_url);
350   } else {
351     GoogleURLTrackerInfoBarDelegate* infobar =
352         infobar_creator_.Run(infobar_service, this, search_url);
353     if (infobar)
354       map_entry->SetInfoBarDelegate(infobar);
355     else
356       map_entry->Close(false);
357   }
358 }
359
360 void GoogleURLTracker::OnTabClosed(
361     content::NavigationController* navigation_controller) {
362   // Because InfoBarService tears itself down on tab destruction, it's possible
363   // to get a non-NULL InfoBarService pointer here, depending on which order
364   // notifications fired in.  Likewise, the pointer in |entry_map_| (and in its
365   // associated MapEntry) may point to deleted memory.  Therefore, if we were to
366   // access the InfoBarService* we have for this tab, we'd need to ensure we
367   // just looked at the raw pointer value, and never dereferenced it.  This
368   // function doesn't need to do even that, but others in the call chain from
369   // here might (and have comments pointing back here).
370   for (EntryMap::iterator i(entry_map_.begin()); i != entry_map_.end(); ++i) {
371     if (i->second->navigation_controller() == navigation_controller) {
372       i->second->Close(false);
373       return;
374     }
375   }
376   NOTREACHED();
377 }
378
379 void GoogleURLTracker::CloseAllEntries(bool redo_searches) {
380   // Delete all entries, whether they have infobars or not.
381   while (!entry_map_.empty())
382     entry_map_.begin()->second->Close(redo_searches);
383 }
384
385 void GoogleURLTracker::UnregisterForEntrySpecificNotifications(
386     const GoogleURLTrackerMapEntry& map_entry,
387     bool must_be_listening_for_commit) {
388   // For tabs with map entries but no infobars, we should always be listening
389   // for both these notifications.  For tabs with infobars, we may be listening
390   // for navigation commits if the user has performed a new search on this tab.
391   if (nav_helper_->IsListeningForNavigationCommit(
392           map_entry.navigation_controller())) {
393     nav_helper_->SetListeningForNavigationCommit(
394         map_entry.navigation_controller(), false);
395   } else {
396     DCHECK(!must_be_listening_for_commit);
397     DCHECK(map_entry.has_infobar_delegate());
398   }
399   const bool registered_for_tab_destruction =
400       nav_helper_->IsListeningForTabDestruction(
401           map_entry.navigation_controller());
402   DCHECK_NE(registered_for_tab_destruction, map_entry.has_infobar_delegate());
403   if (registered_for_tab_destruction) {
404     nav_helper_->SetListeningForTabDestruction(
405         map_entry.navigation_controller(), false);
406   }
407
408   // Our global listeners for these other notifications should be in place iff
409   // we have any tabs still listening for commits.  These tabs either have no
410   // infobars or have received new pending searches atop existing infobars; in
411   // either case we want to catch subsequent pending non-search navigations.
412   // See the various cases inside OnNavigationPending().
413   for (EntryMap::const_iterator i(entry_map_.begin()); i != entry_map_.end();
414        ++i) {
415     if (nav_helper_->IsListeningForNavigationCommit(
416             i->second->navigation_controller())) {
417       DCHECK(nav_helper_->IsListeningForNavigationStart());
418       return;
419     }
420   }
421   if (nav_helper_->IsListeningForNavigationStart()) {
422     DCHECK(!search_committed_);
423     nav_helper_->SetListeningForNavigationStart(false);
424   }
425 }