search_provider_install_data.cc revision 90dce4d38c5ff5333bea97d859d4e484e27edf0c
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/search_engines/search_provider_install_data.h"
6
7#include <algorithm>
8#include <functional>
9#include <vector>
10
11#include "base/basictypes.h"
12#include "base/bind.h"
13#include "base/logging.h"
14#include "base/memory/ref_counted.h"
15#include "base/sequenced_task_runner_helpers.h"
16#include "chrome/browser/google/google_url_tracker.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/search_engines/search_host_to_urls_map.h"
19#include "chrome/browser/search_engines/search_terms_data.h"
20#include "chrome/browser/search_engines/template_url.h"
21#include "chrome/browser/search_engines/template_url_service.h"
22#include "chrome/browser/search_engines/util.h"
23#include "chrome/browser/webdata/web_data_service.h"
24#include "chrome/common/chrome_notification_types.h"
25#include "content/public/browser/browser_thread.h"
26#include "content/public/browser/notification_observer.h"
27#include "content/public/browser/notification_registrar.h"
28#include "content/public/browser/notification_service.h"
29#include "content/public/browser/notification_source.h"
30
31using content::BrowserThread;
32
33typedef SearchHostToURLsMap::TemplateURLSet TemplateURLSet;
34
35namespace {
36
37// Implementation of SearchTermsData that may be used on the I/O thread.
38class IOThreadSearchTermsData : public SearchTermsData {
39 public:
40  explicit IOThreadSearchTermsData(const std::string& google_base_url);
41
42  // Implementation of SearchTermsData.
43  virtual std::string GoogleBaseURLValue() const OVERRIDE;
44
45 private:
46  std::string google_base_url_;
47
48  DISALLOW_COPY_AND_ASSIGN(IOThreadSearchTermsData);
49};
50
51IOThreadSearchTermsData::IOThreadSearchTermsData(
52    const std::string& google_base_url) : google_base_url_(google_base_url) {
53}
54
55std::string IOThreadSearchTermsData::GoogleBaseURLValue() const {
56  return google_base_url_;
57}
58
59// Handles telling SearchProviderInstallData about changes to the google base
60// url. (Ensure that this is deleted on the I/O thread so that the WeakPtr is
61// deleted on the correct thread.)
62class GoogleURLChangeNotifier
63    : public base::RefCountedThreadSafe<GoogleURLChangeNotifier,
64                                        BrowserThread::DeleteOnIOThread> {
65 public:
66  explicit GoogleURLChangeNotifier(
67      const base::WeakPtr<SearchProviderInstallData>& install_data);
68
69  // Called on the I/O thread with the Google base URL whenever the value
70  // changes.
71  void OnChange(const std::string& google_base_url);
72
73 private:
74  friend struct BrowserThread::DeleteOnThread<BrowserThread::IO>;
75  friend class base::DeleteHelper<GoogleURLChangeNotifier>;
76
77  ~GoogleURLChangeNotifier() {}
78
79  base::WeakPtr<SearchProviderInstallData> install_data_;
80
81  DISALLOW_COPY_AND_ASSIGN(GoogleURLChangeNotifier);
82};
83
84GoogleURLChangeNotifier::GoogleURLChangeNotifier(
85    const base::WeakPtr<SearchProviderInstallData>& install_data)
86    : install_data_(install_data) {
87}
88
89void GoogleURLChangeNotifier::OnChange(const std::string& google_base_url) {
90  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
91  if (install_data_)
92    install_data_->OnGoogleURLChange(google_base_url);
93}
94
95// Notices changes in the Google base URL and sends them along
96// to the SearchProviderInstallData on the I/O thread.
97class GoogleURLObserver : public content::NotificationObserver {
98 public:
99  GoogleURLObserver(Profile* profile,
100                    GoogleURLChangeNotifier* change_notifier,
101                    int ui_death_notification,
102                    const content::NotificationSource& ui_death_source);
103
104  // Implementation of content::NotificationObserver.
105  virtual void Observe(int type,
106                       const content::NotificationSource& source,
107                       const content::NotificationDetails& details) OVERRIDE;
108
109 private:
110  virtual ~GoogleURLObserver() {}
111
112  scoped_refptr<GoogleURLChangeNotifier> change_notifier_;
113  content::NotificationRegistrar registrar_;
114
115  DISALLOW_COPY_AND_ASSIGN(GoogleURLObserver);
116};
117
118GoogleURLObserver::GoogleURLObserver(
119    Profile* profile,
120    GoogleURLChangeNotifier* change_notifier,
121    int ui_death_notification,
122    const content::NotificationSource& ui_death_source)
123    : change_notifier_(change_notifier) {
124  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
125  registrar_.Add(this, chrome::NOTIFICATION_GOOGLE_URL_UPDATED,
126                 content::Source<Profile>(profile->GetOriginalProfile()));
127  registrar_.Add(this, ui_death_notification, ui_death_source);
128}
129
130void GoogleURLObserver::Observe(int type,
131                                const content::NotificationSource& source,
132                                const content::NotificationDetails& details) {
133  if (type == chrome::NOTIFICATION_GOOGLE_URL_UPDATED) {
134    BrowserThread::PostTask(BrowserThread::IO, FROM_HERE,
135        base::Bind(&GoogleURLChangeNotifier::OnChange, change_notifier_.get(),
136            content::Details<GoogleURLTracker::UpdatedDetails>(details)->second.
137                spec()));
138  } else {
139    // This must be the death notification.
140    delete this;
141  }
142}
143
144// Indicates if the two inputs have the same security origin.
145// |requested_origin| should only be a security origin (no path, etc.).
146// It is ok if |template_url| is NULL.
147static bool IsSameOrigin(const GURL& requested_origin,
148                         TemplateURL* template_url,
149                         const SearchTermsData& search_terms_data) {
150  DCHECK(requested_origin == requested_origin.GetOrigin());
151  DCHECK(!template_url->IsExtensionKeyword());
152  return requested_origin ==
153      TemplateURLService::GenerateSearchURLUsingTermsData(template_url,
154          search_terms_data).GetOrigin();
155}
156
157}  // namespace
158
159SearchProviderInstallData::SearchProviderInstallData(
160    Profile* profile,
161    int ui_death_notification,
162    const content::NotificationSource& ui_death_source)
163    : web_service_(WebDataService::FromBrowserContext(profile)),
164      load_handle_(0),
165      google_base_url_(UIThreadSearchTermsData(profile).GoogleBaseURLValue()) {
166  // GoogleURLObserver is responsible for killing itself when
167  // the given notification occurs.
168  new GoogleURLObserver(profile, new GoogleURLChangeNotifier(AsWeakPtr()),
169                        ui_death_notification, ui_death_source);
170}
171
172SearchProviderInstallData::~SearchProviderInstallData() {
173  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
174
175  if (load_handle_) {
176    DCHECK(web_service_.get());
177    web_service_->CancelRequest(load_handle_);
178  }
179}
180
181void SearchProviderInstallData::CallWhenLoaded(const base::Closure& closure) {
182  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
183
184  if (provider_map_.get()) {
185    closure.Run();
186    return;
187  }
188
189  closure_queue_.push_back(closure);
190  if (load_handle_)
191    return;
192
193  if (web_service_.get())
194    load_handle_ = web_service_->GetKeywords(this);
195  else
196    OnLoadFailed();
197}
198
199SearchProviderInstallData::State SearchProviderInstallData::GetInstallState(
200    const GURL& requested_origin) {
201  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
202  DCHECK(provider_map_.get());
203
204  // First check to see if the origin is the default search provider.
205  if (requested_origin.spec() == default_search_origin_)
206    return INSTALLED_AS_DEFAULT;
207
208  // Is the url any search provider?
209  const TemplateURLSet* urls = provider_map_->GetURLsForHost(
210      requested_origin.host());
211  if (!urls)
212    return NOT_INSTALLED;
213
214  IOThreadSearchTermsData search_terms_data(google_base_url_);
215  for (TemplateURLSet::const_iterator i = urls->begin();
216       i != urls->end(); ++i) {
217    if (IsSameOrigin(requested_origin, *i, search_terms_data))
218      return INSTALLED_BUT_NOT_DEFAULT;
219  }
220  return NOT_INSTALLED;
221}
222
223void SearchProviderInstallData::OnGoogleURLChange(
224    const std::string& google_base_url) {
225  google_base_url_ = google_base_url;
226}
227
228void SearchProviderInstallData::OnWebDataServiceRequestDone(
229    WebDataService::Handle h,
230    const WDTypedResult* result) {
231  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
232
233  // Reset the load_handle so that we don't try and cancel the load in
234  // the destructor.
235  load_handle_ = 0;
236
237  if (!result) {
238    // Results are null if the database went away or (most likely) wasn't
239    // loaded.
240    OnLoadFailed();
241    return;
242  }
243
244  TemplateURL* default_search_provider = NULL;
245  int new_resource_keyword_version = 0;
246  std::vector<TemplateURL*> extracted_template_urls;
247  GetSearchProvidersUsingKeywordResult(*result, NULL, NULL,
248      &extracted_template_urls, &default_search_provider,
249      &new_resource_keyword_version, NULL);
250  template_urls_.get().insert(template_urls_.get().begin(),
251                              extracted_template_urls.begin(),
252                              extracted_template_urls.end());
253  IOThreadSearchTermsData search_terms_data(google_base_url_);
254  provider_map_.reset(new SearchHostToURLsMap());
255  provider_map_->Init(template_urls_.get(), search_terms_data);
256  SetDefault(default_search_provider);
257  NotifyLoaded();
258}
259
260void SearchProviderInstallData::SetDefault(const TemplateURL* template_url) {
261  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
262
263  if (!template_url) {
264    default_search_origin_.clear();
265    return;
266  }
267
268  DCHECK(!template_url->IsExtensionKeyword());
269
270  IOThreadSearchTermsData search_terms_data(google_base_url_);
271  const GURL url(TemplateURLService::GenerateSearchURLUsingTermsData(
272      template_url, search_terms_data));
273  if (!url.is_valid() || !url.has_host()) {
274    default_search_origin_.clear();
275    return;
276  }
277  default_search_origin_ = url.GetOrigin().spec();
278}
279
280void SearchProviderInstallData::OnLoadFailed() {
281  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
282
283  provider_map_.reset(new SearchHostToURLsMap());
284  IOThreadSearchTermsData search_terms_data(google_base_url_);
285  provider_map_->Init(template_urls_.get(), search_terms_data);
286  SetDefault(NULL);
287  NotifyLoaded();
288}
289
290void SearchProviderInstallData::NotifyLoaded() {
291  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
292
293  std::vector<base::Closure> closure_queue;
294  closure_queue.swap(closure_queue_);
295
296  std::for_each(closure_queue.begin(),
297                closure_queue.end(),
298                std::mem_fun_ref(&base::Closure::Run));
299
300  // Since we expect this request to be rare, clear out the information. This
301  // also keeps the responses current as the search providers change.
302  provider_map_.reset();
303  SetDefault(NULL);
304}
305