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/ui/webui/options/search_engine_manager_handler.h"
6
7#include "base/bind.h"
8#include "base/strings/string_number_conversions.h"
9#include "base/strings/utf_string_conversions.h"
10#include "base/values.h"
11#include "chrome/browser/extensions/extension_util.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
14#include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
15#include "chrome/browser/ui/search_engines/template_url_table_model.h"
16#include "chrome/common/url_constants.h"
17#include "chrome/grit/generated_resources.h"
18#include "chrome/grit/locale_settings.h"
19#include "components/search_engines/template_url.h"
20#include "components/search_engines/template_url_service.h"
21#include "content/public/browser/user_metrics.h"
22#include "content/public/browser/web_ui.h"
23#include "extensions/browser/extension_registry.h"
24#include "extensions/common/extension.h"
25#include "ui/base/l10n/l10n_util.h"
26
27namespace {
28
29enum EngineInfoIndexes {
30  ENGINE_NAME,
31  ENGINE_KEYWORD,
32  ENGINE_URL,
33};
34
35};  // namespace
36
37namespace options {
38
39SearchEngineManagerHandler::SearchEngineManagerHandler() {
40}
41
42SearchEngineManagerHandler::~SearchEngineManagerHandler() {
43  if (list_controller_.get() && list_controller_->table_model())
44    list_controller_->table_model()->SetObserver(NULL);
45}
46
47void SearchEngineManagerHandler::InitializeHandler() {
48  list_controller_.reset(
49      new KeywordEditorController(Profile::FromWebUI(web_ui())));
50  DCHECK(list_controller_.get());
51  list_controller_->table_model()->SetObserver(this);
52}
53
54void SearchEngineManagerHandler::InitializePage() {
55  OnModelChanged();
56}
57
58void SearchEngineManagerHandler::GetLocalizedValues(
59    base::DictionaryValue* localized_strings) {
60  DCHECK(localized_strings);
61
62  RegisterTitle(localized_strings, "searchEngineManagerPage",
63                IDS_SEARCH_ENGINES_EDITOR_WINDOW_TITLE);
64  localized_strings->SetString("defaultSearchEngineListTitle",
65      l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAIN_SEPARATOR));
66  localized_strings->SetString("otherSearchEngineListTitle",
67      l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_OTHER_SEPARATOR));
68  localized_strings->SetString("extensionKeywordsListTitle",
69      l10n_util::GetStringUTF16(
70          IDS_SEARCH_ENGINES_EDITOR_EXTENSIONS_SEPARATOR));
71  localized_strings->SetString("makeDefaultSearchEngineButton",
72      l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_EDITOR_MAKE_DEFAULT_BUTTON));
73  localized_strings->SetString("searchEngineTableNamePlaceholder",
74      l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_NAME_PLACEHOLDER));
75  localized_strings->SetString("searchEngineTableKeywordPlaceholder",
76      l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_KEYWORD_PLACEHOLDER));
77  localized_strings->SetString("searchEngineTableURLPlaceholder",
78      l10n_util::GetStringUTF16(IDS_SEARCH_ENGINE_ADD_NEW_URL_PLACEHOLDER));
79  localized_strings->SetString("editSearchEngineInvalidTitleToolTip",
80      l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_TITLE_TT));
81  localized_strings->SetString("editSearchEngineInvalidKeywordToolTip",
82      l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_KEYWORD_TT));
83  localized_strings->SetString("editSearchEngineInvalidURLToolTip",
84      l10n_util::GetStringUTF16(IDS_SEARCH_ENGINES_INVALID_URL_TT));
85}
86
87void SearchEngineManagerHandler::RegisterMessages() {
88  web_ui()->RegisterMessageCallback(
89      "managerSetDefaultSearchEngine",
90      base::Bind(&SearchEngineManagerHandler::SetDefaultSearchEngine,
91                 base::Unretained(this)));
92  web_ui()->RegisterMessageCallback(
93      "removeSearchEngine",
94      base::Bind(&SearchEngineManagerHandler::RemoveSearchEngine,
95                 base::Unretained(this)));
96  web_ui()->RegisterMessageCallback(
97      "editSearchEngine",
98      base::Bind(&SearchEngineManagerHandler::EditSearchEngine,
99                 base::Unretained(this)));
100  web_ui()->RegisterMessageCallback(
101      "checkSearchEngineInfoValidity",
102      base::Bind(&SearchEngineManagerHandler::CheckSearchEngineInfoValidity,
103                 base::Unretained(this)));
104  web_ui()->RegisterMessageCallback(
105      "searchEngineEditCancelled",
106      base::Bind(&SearchEngineManagerHandler::EditCancelled,
107                 base::Unretained(this)));
108  web_ui()->RegisterMessageCallback(
109      "searchEngineEditCompleted",
110      base::Bind(&SearchEngineManagerHandler::EditCompleted,
111                 base::Unretained(this)));
112}
113
114void SearchEngineManagerHandler::OnModelChanged() {
115  DCHECK(list_controller_.get());
116  if (!list_controller_->loaded())
117    return;
118
119  // Find the default engine.
120  const TemplateURL* default_engine =
121      list_controller_->GetDefaultSearchProvider();
122  int default_index = list_controller_->table_model()->IndexOfTemplateURL(
123      default_engine);
124
125  // Build the first list (default search engine options).
126  base::ListValue defaults_list;
127  int last_default_engine_index =
128      list_controller_->table_model()->last_search_engine_index();
129  for (int i = 0; i < last_default_engine_index; ++i) {
130    // Third argument is false, as the engine is not from an extension.
131    defaults_list.Append(CreateDictionaryForEngine(
132        i, i == default_index, false));
133  }
134
135  // Build the second list (other search templates).
136  base::ListValue others_list;
137  int last_other_engine_index =
138      list_controller_->table_model()->last_other_engine_index();
139  if (last_default_engine_index < 0)
140    last_default_engine_index = 0;
141  for (int i = last_default_engine_index; i < last_other_engine_index; ++i) {
142    others_list.Append(CreateDictionaryForEngine(i, i == default_index, false));
143  }
144
145  // Build the extension keywords list.
146  base::ListValue keyword_list;
147  if (last_other_engine_index < 0)
148    last_other_engine_index = 0;
149  int engine_count = list_controller_->table_model()->RowCount();
150  for (int i = last_other_engine_index; i < engine_count; ++i) {
151    keyword_list.Append(CreateDictionaryForEngine(i, i == default_index, true));
152  }
153
154  web_ui()->CallJavascriptFunction("SearchEngineManager.updateSearchEngineList",
155                                   defaults_list, others_list, keyword_list);
156}
157
158void SearchEngineManagerHandler::OnItemsChanged(int start, int length) {
159  OnModelChanged();
160}
161
162void SearchEngineManagerHandler::OnItemsAdded(int start, int length) {
163  OnModelChanged();
164}
165
166void SearchEngineManagerHandler::OnItemsRemoved(int start, int length) {
167  OnModelChanged();
168}
169
170base::DictionaryValue* SearchEngineManagerHandler::CreateDictionaryForEngine(
171    int index, bool is_default, bool is_extension) {
172  TemplateURLTableModel* table_model = list_controller_->table_model();
173  const TemplateURL* template_url = list_controller_->GetTemplateURL(index);
174
175  // The items which are to be written into |dict| are also described in
176  // chrome/browser/resources/options/search_engine_manager_engine_list.js
177  // in @typedef for SearchEngine. Please update it whenever you add or remove
178  // any keys here.
179  base::DictionaryValue* dict = new base::DictionaryValue();
180  dict->SetString("name",  template_url->short_name());
181  dict->SetString("displayName", table_model->GetText(
182    index, IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN));
183  dict->SetString("keyword", table_model->GetText(
184    index, IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN));
185  dict->SetString("url", template_url->url_ref().DisplayURL(
186      UIThreadSearchTermsData(Profile::FromWebUI(web_ui()))));
187  dict->SetBoolean("urlLocked", template_url->prepopulate_id() > 0);
188  GURL icon_url = template_url->favicon_url();
189  if (icon_url.is_valid())
190    dict->SetString("iconURL", icon_url.spec());
191  dict->SetString("modelIndex", base::IntToString(index));
192
193  dict->SetBoolean("canBeRemoved",
194      list_controller_->CanRemove(template_url) && !is_extension);
195  dict->SetBoolean("canBeDefault",
196      list_controller_->CanMakeDefault(template_url) && !is_extension);
197  dict->SetBoolean("default", is_default);
198  dict->SetBoolean("canBeEdited", list_controller_->CanEdit(template_url));
199  dict->SetBoolean("isExtension", is_extension);
200  if (template_url->GetType() == TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION) {
201    const extensions::Extension* extension =
202        extensions::ExtensionRegistry::Get(Profile::FromWebUI(web_ui()))
203            ->GetExtensionById(template_url->GetExtensionId(),
204                               extensions::ExtensionRegistry::EVERYTHING);
205    if (extension) {
206      dict->Set("extension",
207                extensions::util::GetExtensionInfo(extension).release());
208    }
209  }
210  return dict;
211}
212
213void SearchEngineManagerHandler::SetDefaultSearchEngine(
214    const base::ListValue* args) {
215  int index;
216  if (!ExtractIntegerValue(args, &index)) {
217    NOTREACHED();
218    return;
219  }
220  if (index < 0 || index >= list_controller_->table_model()->RowCount())
221    return;
222
223  list_controller_->MakeDefaultTemplateURL(index);
224
225  content::RecordAction(
226      base::UserMetricsAction("Options_SearchEngineSetDefault"));
227}
228
229void SearchEngineManagerHandler::RemoveSearchEngine(
230    const base::ListValue* args) {
231  int index;
232  if (!ExtractIntegerValue(args, &index)) {
233    NOTREACHED();
234    return;
235  }
236  if (index < 0 || index >= list_controller_->table_model()->RowCount())
237    return;
238
239  if (list_controller_->CanRemove(list_controller_->GetTemplateURL(index))) {
240    list_controller_->RemoveTemplateURL(index);
241    content::RecordAction(
242        base::UserMetricsAction("Options_SearchEngineRemoved"));
243  }
244}
245
246void SearchEngineManagerHandler::EditSearchEngine(const base::ListValue* args) {
247  int index;
248  if (!ExtractIntegerValue(args, &index)) {
249    NOTREACHED();
250    return;
251  }
252
253  // Allow -1, which means we are adding a new engine.
254  if (index < -1 || index >= list_controller_->table_model()->RowCount())
255    return;
256
257  edit_controller_.reset(new EditSearchEngineController(
258      (index == -1) ? NULL : list_controller_->GetTemplateURL(index), this,
259      Profile::FromWebUI(web_ui())));
260}
261
262void SearchEngineManagerHandler::OnEditedKeyword(
263    TemplateURL* template_url,
264    const base::string16& title,
265    const base::string16& keyword,
266    const std::string& url) {
267  DCHECK(!url.empty());
268  if (template_url)
269    list_controller_->ModifyTemplateURL(template_url, title, keyword, url);
270  else
271    list_controller_->AddTemplateURL(title, keyword, url);
272  edit_controller_.reset();
273}
274
275void SearchEngineManagerHandler::CheckSearchEngineInfoValidity(
276    const base::ListValue* args) {
277  if (!edit_controller_.get())
278    return;
279  base::string16 name;
280  base::string16 keyword;
281  std::string url;
282  std::string modelIndex;
283  if (!args->GetString(ENGINE_NAME, &name) ||
284      !args->GetString(ENGINE_KEYWORD, &keyword) ||
285      !args->GetString(ENGINE_URL, &url) ||
286      !args->GetString(3, &modelIndex)) {
287    NOTREACHED();
288    return;
289  }
290
291  base::DictionaryValue validity;
292  validity.SetBoolean("name", edit_controller_->IsTitleValid(name));
293  validity.SetBoolean("keyword", edit_controller_->IsKeywordValid(keyword));
294  validity.SetBoolean("url", edit_controller_->IsURLValid(url));
295  base::StringValue indexValue(modelIndex);
296  web_ui()->CallJavascriptFunction("SearchEngineManager.validityCheckCallback",
297                                   validity, indexValue);
298}
299
300void SearchEngineManagerHandler::EditCancelled(const base::ListValue* args) {
301  if (!edit_controller_.get())
302    return;
303  edit_controller_->CleanUpCancelledAdd();
304  edit_controller_.reset();
305}
306
307void SearchEngineManagerHandler::EditCompleted(const base::ListValue* args) {
308  if (!edit_controller_.get())
309    return;
310  base::string16 name;
311  base::string16 keyword;
312  std::string url;
313  if (!args->GetString(ENGINE_NAME, &name) ||
314      !args->GetString(ENGINE_KEYWORD, &keyword) ||
315      !args->GetString(ENGINE_URL, &url)) {
316    NOTREACHED();
317    return;
318  }
319
320  // Recheck validity.  It's possible to get here with invalid input if e.g. the
321  // user calls the right JS functions directly from the web inspector.
322  if (edit_controller_->IsTitleValid(name) &&
323      edit_controller_->IsKeywordValid(keyword) &&
324      edit_controller_->IsURLValid(url))
325    edit_controller_->AcceptAddOrEdit(name, keyword, url);
326}
327
328}  // namespace options
329