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