search_engine_manager_handler.cc revision f2477e01787aa58f445919b809d89e252beef54f
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_service.h"
12#include "chrome/browser/extensions/extension_system.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/search_engines/template_url.h"
15#include "chrome/browser/search_engines/template_url_service.h"
16#include "chrome/browser/ui/search_engines/keyword_editor_controller.h"
17#include "chrome/browser/ui/search_engines/template_url_table_model.h"
18#include "chrome/common/extensions/api/omnibox/omnibox_handler.h"
19#include "chrome/common/extensions/manifest_handlers/icons_handler.h"
20#include "chrome/common/url_constants.h"
21#include "content/public/browser/web_ui.h"
22#include "extensions/common/extension.h"
23#include "grit/generated_resources.h"
24#include "grit/locale_settings.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_->url_model()->GetDefaultSearchProvider();
122  int default_index = list_controller_->table_model()->IndexOfTemplateURL(
123      default_engine);
124
125  // Build the first list (default search engine options).
126  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  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  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  base::DictionaryValue* dict = new base::DictionaryValue();
176  dict->SetString("name",  template_url->short_name());
177  dict->SetString("displayName", table_model->GetText(
178    index, IDS_SEARCH_ENGINES_EDITOR_DESCRIPTION_COLUMN));
179  dict->SetString("keyword", table_model->GetText(
180    index, IDS_SEARCH_ENGINES_EDITOR_KEYWORD_COLUMN));
181  dict->SetString("url", template_url->url_ref().DisplayURL());
182  dict->SetBoolean("urlLocked", template_url->prepopulate_id() > 0);
183  GURL icon_url = template_url->favicon_url();
184  if (icon_url.is_valid())
185    dict->SetString("iconURL", icon_url.spec());
186  dict->SetString("modelIndex", base::IntToString(index));
187
188  dict->SetBoolean("canBeRemoved",
189      list_controller_->CanRemove(template_url) && !is_extension);
190  dict->SetBoolean("canBeDefault",
191      list_controller_->CanMakeDefault(template_url) && !is_extension);
192  dict->SetBoolean("default", is_default);
193  dict->SetBoolean("canBeEdited", list_controller_->CanEdit(template_url));
194  dict->SetBoolean("isExtension", is_extension);
195  if (template_url->GetType() == TemplateURL::NORMAL_CONTROLLED_BY_EXTENSION) {
196    std::string extension_id = template_url->GetExtensionId();
197    ExtensionService* extension_service = extensions::ExtensionSystem::Get(
198        Profile::FromWebUI(web_ui()))->extension_service();
199    scoped_ptr<DictionaryValue> dictionary =
200        extension_service->GetExtensionInfo(extension_id);
201    if (!dictionary->empty())
202      dict->Set("extension", dictionary.release());
203  }
204  return dict;
205}
206
207void SearchEngineManagerHandler::SetDefaultSearchEngine(const ListValue* args) {
208  int index;
209  if (!ExtractIntegerValue(args, &index)) {
210    NOTREACHED();
211    return;
212  }
213  if (index < 0 || index >= list_controller_->table_model()->RowCount())
214    return;
215
216  list_controller_->MakeDefaultTemplateURL(index);
217}
218
219void SearchEngineManagerHandler::RemoveSearchEngine(const ListValue* args) {
220  int index;
221  if (!ExtractIntegerValue(args, &index)) {
222    NOTREACHED();
223    return;
224  }
225  if (index < 0 || index >= list_controller_->table_model()->RowCount())
226    return;
227
228  if (list_controller_->CanRemove(list_controller_->GetTemplateURL(index)))
229    list_controller_->RemoveTemplateURL(index);
230}
231
232void SearchEngineManagerHandler::EditSearchEngine(const ListValue* args) {
233  int index;
234  if (!ExtractIntegerValue(args, &index)) {
235    NOTREACHED();
236    return;
237  }
238
239  // Allow -1, which means we are adding a new engine.
240  if (index < -1 || index >= list_controller_->table_model()->RowCount())
241    return;
242
243  edit_controller_.reset(new EditSearchEngineController(
244      (index == -1) ? NULL : list_controller_->GetTemplateURL(index), this,
245      Profile::FromWebUI(web_ui())));
246}
247
248void SearchEngineManagerHandler::OnEditedKeyword(
249    TemplateURL* template_url,
250    const string16& title,
251    const string16& keyword,
252    const std::string& url) {
253  DCHECK(!url.empty());
254  if (template_url)
255    list_controller_->ModifyTemplateURL(template_url, title, keyword, url);
256  else
257    list_controller_->AddTemplateURL(title, keyword, url);
258  edit_controller_.reset();
259}
260
261void SearchEngineManagerHandler::CheckSearchEngineInfoValidity(
262    const ListValue* args)
263{
264  if (!edit_controller_.get())
265    return;
266  string16 name;
267  string16 keyword;
268  std::string url;
269  std::string modelIndex;
270  if (!args->GetString(ENGINE_NAME, &name) ||
271      !args->GetString(ENGINE_KEYWORD, &keyword) ||
272      !args->GetString(ENGINE_URL, &url) ||
273      !args->GetString(3, &modelIndex)) {
274    NOTREACHED();
275    return;
276  }
277
278  base::DictionaryValue validity;
279  validity.SetBoolean("name", edit_controller_->IsTitleValid(name));
280  validity.SetBoolean("keyword", edit_controller_->IsKeywordValid(keyword));
281  validity.SetBoolean("url", edit_controller_->IsURLValid(url));
282  StringValue indexValue(modelIndex);
283  web_ui()->CallJavascriptFunction("SearchEngineManager.validityCheckCallback",
284                                   validity, indexValue);
285}
286
287void SearchEngineManagerHandler::EditCancelled(const ListValue* args) {
288  if (!edit_controller_.get())
289    return;
290  edit_controller_->CleanUpCancelledAdd();
291  edit_controller_.reset();
292}
293
294void SearchEngineManagerHandler::EditCompleted(const ListValue* args) {
295  if (!edit_controller_.get())
296    return;
297  string16 name;
298  string16 keyword;
299  std::string url;
300  if (!args->GetString(ENGINE_NAME, &name) ||
301      !args->GetString(ENGINE_KEYWORD, &keyword) ||
302      !args->GetString(ENGINE_URL, &url)) {
303    NOTREACHED();
304    return;
305  }
306
307  // Recheck validity.  It's possible to get here with invalid input if e.g. the
308  // user calls the right JS functions directly from the web inspector.
309  if (edit_controller_->IsTitleValid(name) &&
310      edit_controller_->IsKeywordValid(keyword) &&
311      edit_controller_->IsURLValid(url))
312    edit_controller_->AcceptAddOrEdit(name, keyword, url);
313}
314
315}  // namespace options
316