Upstream version 5.34.104.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / ui / webui / options / chromeos / cros_language_options_handler.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/ui/webui/options/chromeos/cros_language_options_handler.h"
6
7 #include <algorithm>
8 #include <iterator>
9 #include <map>
10 #include <set>
11 #include <vector>
12
13 #include "base/bind.h"
14 #include "base/bind_helpers.h"
15 #include "base/i18n/rtl.h"
16 #include "base/strings/stringprintf.h"
17 #include "base/strings/utf_string_conversions.h"
18 #include "base/values.h"
19 #include "chrome/app/chrome_command_ids.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/chromeos/customization_document.h"
22 #include "chrome/browser/chromeos/input_method/input_method_util.h"
23 #include "chrome/browser/chromeos/login/user_manager.h"
24 #include "chrome/browser/extensions/extension_service.h"
25 #include "chrome/browser/extensions/extension_tab_util.h"
26 #include "chrome/browser/lifetime/application_lifetime.h"
27 #include "chrome/browser/profiles/profile.h"
28 #include "chrome/browser/ui/browser.h"
29 #include "chrome/browser/ui/browser_finder.h"
30 #include "chrome/browser/ui/browser_window.h"
31 #include "chrome/browser/ui/tabs/tab_strip_model.h"
32 #include "chrome/common/extensions/manifest_url_handler.h"
33 #include "chromeos/ime/component_extension_ime_manager.h"
34 #include "chromeos/ime/extension_ime_util.h"
35 #include "chromeos/ime/input_method_manager.h"
36 #include "content/public/browser/navigation_controller.h"
37 #include "content/public/browser/user_metrics.h"
38 #include "content/public/browser/web_contents.h"
39 #include "extensions/browser/extension_system.h"
40 #include "extensions/common/extension.h"
41 #include "grit/chromium_strings.h"
42 #include "grit/generated_resources.h"
43 #include "ui/base/l10n/l10n_util.h"
44
45 using base::UserMetricsAction;
46
47 namespace {
48 // TODO(zork): Remove this blacklist when fonts are added to Chrome OS.
49 // see: crbug.com/240586
50
51 bool IsBlacklisted(const std::string& language_code) {
52   return language_code == "si"; // Sinhala
53 }
54
55 } // namespace
56
57 namespace chromeos {
58 namespace options {
59
60 const char kVendorOtherLanguagesListDivider[] =
61     "VENDOR_OTHER_LANGUAGES_LIST_DIVIDER";
62
63 CrosLanguageOptionsHandler::CrosLanguageOptionsHandler()
64     : composition_extension_appended_(false),
65       is_page_initialized_(false) {
66   input_method::InputMethodManager::Get()->GetComponentExtensionIMEManager()->
67       AddObserver(this);
68 }
69
70 CrosLanguageOptionsHandler::~CrosLanguageOptionsHandler() {
71   input_method::InputMethodManager::Get()->GetComponentExtensionIMEManager()->
72       RemoveObserver(this);
73 }
74
75 void CrosLanguageOptionsHandler::GetLocalizedValues(
76     base::DictionaryValue* localized_strings) {
77   ::options::LanguageOptionsHandlerCommon::GetLocalizedValues(
78       localized_strings);
79
80   RegisterTitle(localized_strings, "languagePage",
81                 IDS_OPTIONS_SETTINGS_LANGUAGES_AND_INPUT_DIALOG_TITLE);
82   localized_strings->SetString("okButton", l10n_util::GetStringUTF16(IDS_OK));
83   localized_strings->SetString("configure",
84       l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_CONFIGURE));
85   localized_strings->SetString("inputMethod",
86       l10n_util::GetStringUTF16(IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD));
87   localized_strings->SetString("pleaseAddAnotherInputMethod",
88       l10n_util::GetStringUTF16(
89           IDS_OPTIONS_SETTINGS_LANGUAGES_PLEASE_ADD_ANOTHER_INPUT_METHOD));
90   localized_strings->SetString("inputMethodInstructions",
91       l10n_util::GetStringUTF16(
92           IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_INSTRUCTIONS));
93   localized_strings->SetString("switchInputMethodsHint",
94       l10n_util::GetStringUTF16(
95           IDS_OPTIONS_SETTINGS_LANGUAGES_SWITCH_INPUT_METHODS_HINT));
96   localized_strings->SetString("selectPreviousInputMethodHint",
97       l10n_util::GetStringUTF16(
98           IDS_OPTIONS_SETTINGS_LANGUAGES_SELECT_PREVIOUS_INPUT_METHOD_HINT));
99   localized_strings->SetString("restartButton",
100       l10n_util::GetStringUTF16(
101           IDS_OPTIONS_SETTINGS_LANGUAGES_SIGN_OUT_BUTTON));
102   localized_strings->SetString("extensionImeLable",
103       l10n_util::GetStringUTF16(
104           IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_EXTENSION_IME));
105   localized_strings->SetString("extensionImeDescription",
106       l10n_util::GetStringUTF16(
107           IDS_OPTIONS_SETTINGS_LANGUAGES_INPUT_METHOD_EXTENSION_DESCRIPTION));
108   localized_strings->SetString("noInputMethods",
109       l10n_util::GetStringUTF16(
110           IDS_OPTIONS_SETTINGS_LANGUAGES_NO_INPUT_METHODS));
111
112   input_method::InputMethodManager* manager =
113       input_method::InputMethodManager::Get();
114   // GetSupportedInputMethods() never return NULL.
115   scoped_ptr<input_method::InputMethodDescriptors> descriptors(
116       manager->GetSupportedInputMethods());
117   localized_strings->Set("languageList", GetAcceptLanguageList(*descriptors));
118   localized_strings->Set("inputMethodList", GetInputMethodList(*descriptors));
119
120   input_method::InputMethodDescriptors ext_ime_descriptors;
121   manager->GetInputMethodExtensions(&ext_ime_descriptors);
122   localized_strings->Set("extensionImeList",
123                          ConvertInputMethodDescriptosToIMEList(
124                              ext_ime_descriptors));
125
126   ComponentExtensionIMEManager* component_extension_manager =
127       input_method::InputMethodManager::Get()
128           ->GetComponentExtensionIMEManager();
129   if (component_extension_manager->IsInitialized()) {
130     localized_strings->Set(
131         "componentExtensionImeList",
132         ConvertInputMethodDescriptosToIMEList(
133             component_extension_manager->GetAllIMEAsInputMethodDescriptor()));
134     composition_extension_appended_ = true;
135   } else {
136     // If component extension IME manager is not ready for use, it will be
137     // added in |InitializePage()|.
138     localized_strings->Set("componentExtensionImeList",
139                            new base::ListValue());
140   }
141 }
142
143 void CrosLanguageOptionsHandler::RegisterMessages() {
144   ::options::LanguageOptionsHandlerCommon::RegisterMessages();
145
146   web_ui()->RegisterMessageCallback("inputMethodDisable",
147       base::Bind(&CrosLanguageOptionsHandler::InputMethodDisableCallback,
148                  base::Unretained(this)));
149   web_ui()->RegisterMessageCallback("inputMethodEnable",
150       base::Bind(&CrosLanguageOptionsHandler::InputMethodEnableCallback,
151                  base::Unretained(this)));
152   web_ui()->RegisterMessageCallback("inputMethodOptionsOpen",
153       base::Bind(&CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback,
154                  base::Unretained(this)));
155   web_ui()->RegisterMessageCallback("uiLanguageRestart",
156       base::Bind(&CrosLanguageOptionsHandler::RestartCallback,
157                  base::Unretained(this)));
158 }
159
160 base::ListValue* CrosLanguageOptionsHandler::GetInputMethodList(
161     const input_method::InputMethodDescriptors& descriptors) {
162   input_method::InputMethodManager* manager =
163       input_method::InputMethodManager::Get();
164
165   base::ListValue* input_method_list = new base::ListValue();
166
167   for (size_t i = 0; i < descriptors.size(); ++i) {
168     const input_method::InputMethodDescriptor& descriptor =
169         descriptors[i];
170     const std::string display_name =
171         manager->GetInputMethodUtil()->GetInputMethodDisplayNameFromId(
172             descriptor.id());
173     base::DictionaryValue* dictionary = new base::DictionaryValue();
174     dictionary->SetString("id", descriptor.id());
175     dictionary->SetString("displayName", display_name);
176
177     // One input method can be associated with multiple languages, hence
178     // we use a dictionary here.
179     base::DictionaryValue* languages = new base::DictionaryValue();
180     for (size_t i = 0; i < descriptor.language_codes().size(); ++i) {
181       languages->SetBoolean(descriptor.language_codes().at(i), true);
182     }
183     dictionary->Set("languageCodeSet", languages);
184
185     input_method_list->Append(dictionary);
186   }
187
188   return input_method_list;
189 }
190
191 // static
192 base::ListValue* CrosLanguageOptionsHandler::GetLanguageListInternal(
193     const input_method::InputMethodDescriptors& descriptors,
194     const std::vector<std::string>& base_language_codes,
195     const bool insert_divider) {
196   const std::string app_locale = g_browser_process->GetApplicationLocale();
197
198   std::set<std::string> language_codes;
199   // Collect the language codes from the supported input methods.
200   for (size_t i = 0; i < descriptors.size(); ++i) {
201     const input_method::InputMethodDescriptor& descriptor = descriptors[i];
202     const std::vector<std::string>& languages =
203         descriptor.language_codes();
204     for (size_t i = 0; i < languages.size(); ++i)
205       language_codes.insert(languages[i]);
206   }
207
208   const StartupCustomizationDocument* startup_manifest =
209       StartupCustomizationDocument::GetInstance();
210
211   const std::vector<std::string>& configured_locales =
212       startup_manifest->configured_locales();
213
214   // Languages sort order.
215   std::map<std::string, int /* index */> language_index;
216   for (size_t i = 0; i < configured_locales.size(); ++i) {
217     language_index[configured_locales[i]] = i;
218   }
219
220   // Map of display name -> {language code, native_display_name}.
221   // In theory, we should be able to create a map that is sorted by
222   // display names using ICU comparator, but doing it is hard, thus we'll
223   // use an auxiliary vector to achieve the same result.
224   typedef std::pair<std::string, base::string16> LanguagePair;
225   typedef std::map<base::string16, LanguagePair> LanguageMap;
226   LanguageMap language_map;
227
228   // The auxiliary vector mentioned above. (except vendor locales)
229   std::vector<base::string16> display_names;
230
231   // Separate vector of vendor locales.
232   std::vector<base::string16> configured_locales_display_names(
233       configured_locales.size());
234
235   size_t configured_locales_count = 0;
236   // Build the list of display names, and build the language map.
237   for (std::set<std::string>::const_iterator iter = language_codes.begin();
238        iter != language_codes.end(); ++iter) {
239      // Exclude the language which is not in |base_langauge_codes| even it has
240      // input methods.
241     if (std::find(base_language_codes.begin(),
242                   base_language_codes.end(),
243                   *iter) == base_language_codes.end()) {
244       continue;
245     }
246
247     const base::string16 display_name =
248         l10n_util::GetDisplayNameForLocale(*iter, app_locale, true);
249     const base::string16 native_display_name =
250         l10n_util::GetDisplayNameForLocale(*iter, *iter, true);
251
252     language_map[display_name] =
253         std::make_pair(*iter, native_display_name);
254
255     const std::map<std::string, int>::const_iterator index_pos =
256         language_index.find(*iter);
257     if (index_pos != language_index.end()) {
258       configured_locales_display_names[index_pos->second] = display_name;
259       ++configured_locales_count;
260     } else {
261       display_names.push_back(display_name);
262     }
263   }
264   DCHECK_EQ(display_names.size() + configured_locales_count,
265             language_map.size());
266
267   // Build the list of display names, and build the language map.
268   for (size_t i = 0; i < base_language_codes.size(); ++i) {
269     // Skip this language if it was already added.
270     if (language_codes.find(base_language_codes[i]) != language_codes.end())
271       continue;
272
273     // TODO(zork): Remove this blacklist when fonts are added to Chrome OS.
274     // see: crbug.com/240586
275     if (IsBlacklisted(base_language_codes[i]))
276       continue;
277
278     base::string16 display_name =
279         l10n_util::GetDisplayNameForLocale(
280             base_language_codes[i], app_locale, false);
281     base::string16 native_display_name =
282         l10n_util::GetDisplayNameForLocale(
283             base_language_codes[i], base_language_codes[i], false);
284     language_map[display_name] =
285         std::make_pair(base_language_codes[i], native_display_name);
286
287     const std::map<std::string, int>::const_iterator index_pos =
288         language_index.find(base_language_codes[i]);
289     if (index_pos != language_index.end()) {
290       configured_locales_display_names[index_pos->second] = display_name;
291       ++configured_locales_count;
292     } else {
293       display_names.push_back(display_name);
294     }
295   }
296
297   // Sort display names using locale specific sorter.
298   l10n_util::SortStrings16(app_locale, &display_names);
299   // Concatenate configured_locales_display_names and display_names.
300   // Insert special divider in between.
301   std::vector<base::string16> out_display_names;
302   for (size_t i = 0; i < configured_locales_display_names.size(); ++i) {
303     if (configured_locales_display_names[i].size() == 0)
304       continue;
305     out_display_names.push_back(configured_locales_display_names[i]);
306   }
307
308   base::string16 divider16;
309   if (insert_divider) {
310     divider16 = base::ASCIIToUTF16(
311         insert_divider ? "" : kVendorOtherLanguagesListDivider);
312     out_display_names.push_back(divider16);
313   }
314
315   std::copy(display_names.begin(),
316             display_names.end(),
317             std::back_inserter(out_display_names));
318
319   // Build the language list from the language map.
320   base::ListValue* language_list = new base::ListValue();
321   for (size_t i = 0; i < out_display_names.size(); ++i) {
322     // Sets the directionality of the display language name.
323     base::string16 display_name(out_display_names[i]);
324     if (insert_divider && display_name == divider16) {
325       // Insert divider.
326       base::DictionaryValue* dictionary = new base::DictionaryValue();
327       dictionary->SetString("code", kVendorOtherLanguagesListDivider);
328       language_list->Append(dictionary);
329       continue;
330     }
331     bool markup_removal =
332         base::i18n::UnadjustStringForLocaleDirection(&display_name);
333     DCHECK(markup_removal);
334     bool has_rtl_chars = base::i18n::StringContainsStrongRTLChars(display_name);
335     std::string directionality = has_rtl_chars ? "rtl" : "ltr";
336
337     const LanguagePair& pair = language_map[out_display_names[i]];
338     base::DictionaryValue* dictionary = new base::DictionaryValue();
339     dictionary->SetString("code", pair.first);
340     dictionary->SetString("displayName", out_display_names[i]);
341     dictionary->SetString("textDirection", directionality);
342     dictionary->SetString("nativeDisplayName", pair.second);
343     language_list->Append(dictionary);
344   }
345
346   return language_list;
347 }
348
349 // static
350 base::ListValue* CrosLanguageOptionsHandler::GetAcceptLanguageList(
351     const input_method::InputMethodDescriptors& descriptors) {
352   // Collect the language codes from the supported accept-languages.
353   const std::string app_locale = g_browser_process->GetApplicationLocale();
354   std::vector<std::string> accept_language_codes;
355   l10n_util::GetAcceptLanguagesForLocale(app_locale, &accept_language_codes);
356   return GetLanguageListInternal(descriptors, accept_language_codes, false);
357 }
358
359 // static
360 base::ListValue* CrosLanguageOptionsHandler::GetUILanguageList(
361     const input_method::InputMethodDescriptors& descriptors) {
362   // Collect the language codes from the available locales.
363   return GetLanguageListInternal(
364       descriptors, l10n_util::GetAvailableLocales(), true);
365 }
366
367 base::ListValue*
368     CrosLanguageOptionsHandler::ConvertInputMethodDescriptosToIMEList(
369         const input_method::InputMethodDescriptors& descriptors) {
370   scoped_ptr<base::ListValue> ime_ids_list(new base::ListValue());
371   for (size_t i = 0; i < descriptors.size(); ++i) {
372     const input_method::InputMethodDescriptor& descriptor = descriptors[i];
373     scoped_ptr<base::DictionaryValue> dictionary(new base::DictionaryValue());
374     dictionary->SetString("id", descriptor.id());
375     dictionary->SetString("displayName", descriptor.name());
376     dictionary->SetString("optionsPage", descriptor.options_page_url().spec());
377     scoped_ptr<base::DictionaryValue> language_codes(
378         new base::DictionaryValue());
379     for (size_t i = 0; i < descriptor.language_codes().size(); ++i)
380       language_codes->SetBoolean(descriptor.language_codes().at(i), true);
381     dictionary->Set("languageCodeSet", language_codes.release());
382     ime_ids_list->Append(dictionary.release());
383   }
384   return ime_ids_list.release();
385 }
386
387 base::string16 CrosLanguageOptionsHandler::GetProductName() {
388   return l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_OS_NAME);
389 }
390
391 void CrosLanguageOptionsHandler::SetApplicationLocale(
392     const std::string& language_code) {
393   Profile* profile = Profile::FromWebUI(web_ui());
394   UserManager* user_manager = UserManager::Get();
395
396   // Only the primary user can change the locale.
397   User* user = user_manager->GetUserByProfile(profile);
398   if (user && user->email() == user_manager->GetPrimaryUser()->email()) {
399     profile->ChangeAppLocale(language_code,
400                              Profile::APP_LOCALE_CHANGED_VIA_SETTINGS);
401   }
402 }
403
404 void CrosLanguageOptionsHandler::RestartCallback(const base::ListValue* args) {
405   content::RecordAction(UserMetricsAction("LanguageOptions_SignOut"));
406   chrome::AttemptUserExit();
407 }
408
409 void CrosLanguageOptionsHandler::InputMethodDisableCallback(
410     const base::ListValue* args) {
411   const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args));
412   const std::string action = base::StringPrintf(
413       "LanguageOptions_DisableInputMethod_%s", input_method_id.c_str());
414   content::RecordComputedAction(action);
415 }
416
417 void CrosLanguageOptionsHandler::InputMethodEnableCallback(
418     const base::ListValue* args) {
419   const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args));
420   const std::string action = base::StringPrintf(
421       "LanguageOptions_EnableInputMethod_%s", input_method_id.c_str());
422   content::RecordComputedAction(action);
423 }
424
425 void CrosLanguageOptionsHandler::InputMethodOptionsOpenCallback(
426     const base::ListValue* args) {
427   const std::string input_method_id = UTF16ToASCII(ExtractStringValue(args));
428   const std::string action = base::StringPrintf(
429       "InputMethodOptions_Open_%s", input_method_id.c_str());
430   content::RecordComputedAction(action);
431
432   const std::string extension_id =
433       extension_ime_util::GetExtensionIDFromInputMethodID(input_method_id);
434   if (extension_id.empty())
435     return;
436
437   const input_method::InputMethodDescriptor* ime =
438       input_method::InputMethodManager::Get()->GetInputMethodFromId(
439           input_method_id);
440   if (!ime)
441     return;
442
443   Browser* browser = chrome::FindBrowserWithWebContents(
444       web_ui()->GetWebContents());
445   content::OpenURLParams params(ime->options_page_url(),
446       content::Referrer(),
447       SINGLETON_TAB,
448       content::PAGE_TRANSITION_LINK,
449       false);
450   browser->OpenURL(params);
451   browser->window()->Show();
452   content::WebContents* web_contents =
453       browser->tab_strip_model()->GetActiveWebContents();
454   web_contents->GetDelegate()->ActivateContents(web_contents);
455 }
456
457 void CrosLanguageOptionsHandler::OnInitialized() {
458   if (composition_extension_appended_ || !is_page_initialized_) {
459     // If an option page is not ready to call JavaScript, appending component
460     // extension IMEs will be done in InitializePage function later.
461     return;
462   }
463
464   ComponentExtensionIMEManager* manager =
465       input_method::InputMethodManager::Get()
466           ->GetComponentExtensionIMEManager();
467
468   DCHECK(manager->IsInitialized());
469   scoped_ptr<base::ListValue> ime_list(
470       ConvertInputMethodDescriptosToIMEList(
471           manager->GetAllIMEAsInputMethodDescriptor()));
472   web_ui()->CallJavascriptFunction(
473       "options.LanguageOptions.onComponentManagerInitialized",
474       *ime_list);
475   composition_extension_appended_ = true;
476 }
477
478 void CrosLanguageOptionsHandler::InitializePage() {
479   is_page_initialized_ = true;
480   if (composition_extension_appended_)
481     return;
482
483   ComponentExtensionIMEManager* component_extension_manager =
484       input_method::InputMethodManager::Get()
485           ->GetComponentExtensionIMEManager();
486   if (!component_extension_manager->IsInitialized()) {
487     // If the component extension IME manager is not available yet, append the
488     // component extension list in |OnInitialized()|.
489     return;
490   }
491
492   scoped_ptr<base::ListValue> ime_list(
493       ConvertInputMethodDescriptosToIMEList(
494           component_extension_manager->GetAllIMEAsInputMethodDescriptor()));
495   web_ui()->CallJavascriptFunction(
496       "options.LanguageOptions.onComponentManagerInitialized",
497       *ime_list);
498   composition_extension_appended_ = true;
499 }
500
501 }  // namespace options
502 }  // namespace chromeos