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.
5 // TODO(kochi): Generalize the notification as a component and put it
6 // in js/cr/ui/notification.js .
8 cr.define('options', function() {
9 /** @const */ var Page = cr.ui.pageManager.Page;
10 /** @const */ var PageManager = cr.ui.pageManager.PageManager;
11 /** @const */ var LanguageList = options.LanguageList;
12 /** @const */ var ThirdPartyImeConfirmOverlay =
13 options.ThirdPartyImeConfirmOverlay;
16 * Spell check dictionary download status.
19 /** @const*/ var DOWNLOAD_STATUS = {
25 * The preference is a boolean that enables/disables spell checking.
29 var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking';
32 * The preference is a CSV string that describes preload engines
33 * (i.e. active input methods).
37 var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines';
40 * The preference that lists the extension IMEs that are enabled in the
45 var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes';
48 * The preference that lists the languages which are not translated.
52 var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages';
55 * The preference key that is a list of strings that describes the spellcheck
56 * dictionary language, like ["en-US", "fr"].
60 var SPELL_CHECK_DICTIONARIES_PREF = 'spellcheck.dictionaries';
63 * The preference that indicates if the Translate feature is enabled.
67 var ENABLE_TRANSLATE = 'translate.enabled';
69 /////////////////////////////////////////////////////////////////////////////
70 // LanguageOptions class:
73 * Encapsulated handling of ChromeOS language options page.
75 * @extends {cr.ui.pageManager.Page}
77 function LanguageOptions(model) {
78 Page.call(this, 'languages',
79 loadTimeData.getString('languagePageTabTitle'), 'languagePage');
82 cr.addSingletonGetter(LanguageOptions);
84 // Inherit LanguageOptions from Page.
85 LanguageOptions.prototype = {
86 __proto__: Page.prototype,
89 * For recording the prospective language (the next locale after relaunch).
93 prospectiveUiLanguageCode_: null,
96 * Map from language code to spell check dictionary download status for that
101 spellcheckDictionaryDownloadStatus_: [],
104 * Number of times a spell check dictionary download failed.
108 spellcheckDictionaryDownloadFailures_: 0,
111 * The list of preload engines, like ['mozc', 'pinyin'].
118 * The list of extension IMEs that are enabled out of the language menu.
122 enabledExtensionImes_: [],
125 * The list of the languages which is not translated.
129 translateBlockedLanguages_: [],
132 * The list of the languages supported by Translate server
136 translateSupportedLanguages_: [],
139 * The dictionary of currently selected spellcheck dictionary languages,
140 * like {"en-US": true, "sl-SI": true}.
144 spellCheckLanguages_: {},
147 * The map of language code to input method IDs, like:
148 * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
152 languageCodeToInputMethodIdsMap_: {},
155 * The value that indicates if Translate feature is enabled or not.
159 enableTranslate_: false,
162 * Returns true if the enable-multilingual-spellchecker flag is set.
166 isMultilingualSpellcheckerEnabled_: function() {
167 return loadTimeData.getBoolean('enableMultilingualSpellChecker');
171 initializePage: function() {
172 Page.prototype.initializePage.call(this);
174 var languageOptionsList = $('language-options-list');
175 LanguageList.decorate(languageOptionsList);
177 languageOptionsList.addEventListener('change',
178 this.handleLanguageOptionsListChange_.bind(this));
179 languageOptionsList.addEventListener('save',
180 this.handleLanguageOptionsListSave_.bind(this));
182 this.prospectiveUiLanguageCode_ =
183 loadTimeData.getString('prospectiveUiLanguageCode');
184 this.addEventListener('visibleChange',
185 this.handleVisibleChange_.bind(this));
188 this.initializeInputMethodList_();
189 this.initializeLanguageCodeToInputMethodIdsMap_();
192 var checkbox = $('offer-to-translate-in-this-language');
193 checkbox.addEventListener('click',
194 this.handleOfferToTranslateCheckboxClick_.bind(this));
196 Preferences.getInstance().addEventListener(
197 TRANSLATE_BLOCKED_LANGUAGES_PREF,
198 this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
199 Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARIES_PREF,
200 this.handleSpellCheckDictionariesPrefChange_.bind(this));
201 Preferences.getInstance().addEventListener(ENABLE_TRANSLATE,
202 this.handleEnableTranslatePrefChange_.bind(this));
203 this.translateSupportedLanguages_ =
204 loadTimeData.getValue('translateSupportedLanguages');
206 // Set up add button.
207 var onclick = function(e) {
208 // Add the language without showing the overlay if it's specified in
209 // the URL hash (ex. lang_add=ja). Used for automated testing.
210 var match = document.location.hash.match(/\blang_add=([\w-]+)/);
212 var addLanguageCode = match[1];
213 $('language-options-list').addLanguage(addLanguageCode);
214 this.addBlockedLanguage_(addLanguageCode);
216 PageManager.showPageByName('addLanguage');
217 chrome.send('coreOptionsUserMetricsAction',
218 ['Options_Languages_Add']);
221 $('language-options-add-button').onclick = onclick.bind(this);
224 // Set up the button for editing custom spelling dictionary.
225 $('edit-custom-dictionary-button').onclick = function(e) {
226 PageManager.showPageByName('editDictionary');
228 $('dictionary-download-retry-button').onclick = function(e) {
229 chrome.send('retryDictionaryDownload',
230 [e.currentTarget.languageCode]);
234 // Listen to add language dialog ok button.
235 $('add-language-overlay-ok-button').addEventListener(
236 'click', this.handleAddLanguageOkButtonClick_.bind(this));
238 if (!cr.isChromeOS) {
239 // Show experimental features if enabled.
240 if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
241 $('auto-spell-correction-option').hidden = false;
244 if (!(cr.isMac || cr.isChromeOS)) {
245 // Handle spell check enable/disable.
246 if (!this.isMultilingualSpellcheckerEnabled_()) {
247 Preferences.getInstance().addEventListener(
248 ENABLE_SPELL_CHECK_PREF, this.updateEnableSpellCheck_.bind(this));
250 $('enable-spellcheck-container').hidden =
251 this.isMultilingualSpellcheckerEnabled_();
254 // Handle clicks on "Use this language for spell checking" button.
256 if (this.isMultilingualSpellcheckerEnabled_()) {
257 $('spellcheck-language-checkbox').addEventListener(
259 this.handleSpellCheckLanguageCheckboxClick_.bind(this));
261 $('spellcheck-language-button').addEventListener(
263 this.handleSpellCheckLanguageButtonClick_.bind(this));
268 $('language-options-ui-restart-button').onclick = function() {
269 chrome.send('uiLanguageRestart');
273 $('language-confirm').onclick =
274 PageManager.closeOverlay.bind(PageManager);
276 // Public session users cannot change the locale.
277 if (cr.isChromeOS && UIAccountTweaks.loggedInAsPublicAccount())
278 $('language-options-ui-language-section').hidden = true;
279 PageManager.closeOverlay.bind(PageManager);
283 * Initializes the input method list.
285 initializeInputMethodList_: function() {
286 var inputMethodList = $('language-options-input-method-list');
287 var inputMethodPrototype = $('language-options-input-method-template');
289 // Add all input methods, but make all of them invisible here. We'll
290 // change the visibility in handleLanguageOptionsListChange_() based
291 // on the selected language. Note that we only have less than 100
292 // input methods, so creating DOM nodes at once here should be ok.
293 this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
294 this.appendComponentExtensionIme_(
295 loadTimeData.getValue('componentExtensionImeList'));
296 this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
298 // Listen to pref change once the input method list is initialized.
299 Preferences.getInstance().addEventListener(
300 PRELOAD_ENGINES_PREF,
301 this.handlePreloadEnginesPrefChange_.bind(this));
302 Preferences.getInstance().addEventListener(
303 ENABLED_EXTENSION_IME_PREF,
304 this.handleEnabledExtensionsPrefChange_.bind(this));
308 * Appends input method lists based on component extension ime list.
309 * @param {!Array} componentExtensionImeList A list of input method
313 appendComponentExtensionIme_: function(componentExtensionImeList) {
314 this.appendInputMethodElement_(componentExtensionImeList);
316 for (var i = 0; i < componentExtensionImeList.length; i++) {
317 var inputMethod = componentExtensionImeList[i];
318 for (var languageCode in inputMethod.languageCodeSet) {
319 if (languageCode in this.languageCodeToInputMethodIdsMap_) {
320 this.languageCodeToInputMethodIdsMap_[languageCode].push(
323 this.languageCodeToInputMethodIdsMap_[languageCode] =
331 * Appends input methods into input method list.
332 * @param {!Array} inputMethods A list of input method descriptors.
335 appendInputMethodElement_: function(inputMethods) {
336 var inputMethodList = $('language-options-input-method-list');
337 var inputMethodTemplate = $('language-options-input-method-template');
339 for (var i = 0; i < inputMethods.length; i++) {
340 var inputMethod = inputMethods[i];
341 var element = inputMethodTemplate.cloneNode(true);
343 element.languageCodeSet = inputMethod.languageCodeSet;
345 var input = element.querySelector('input');
346 input.inputMethodId = inputMethod.id;
347 input.imeProvider = inputMethod.extensionName;
348 var span = element.querySelector('span');
349 span.textContent = inputMethod.displayName;
351 if (inputMethod.optionsPage) {
352 var button = document.createElement('button');
353 button.textContent = loadTimeData.getString('configure');
354 button.inputMethodId = inputMethod.id;
355 button.onclick = function(inputMethodId, e) {
356 chrome.send('inputMethodOptionsOpen', [inputMethodId]);
357 }.bind(this, inputMethod.id);
358 element.appendChild(button);
361 // Listen to user clicks.
362 input.addEventListener('click',
363 this.handleCheckboxClick_.bind(this));
364 inputMethodList.appendChild(element);
369 * Adds a language to the preference 'translate_blocked_languages'. If
370 * |langCode| is already added, nothing happens. |langCode| is converted
371 * to a Translate language synonym before added.
372 * @param {string} langCode A language code like 'en'
375 addBlockedLanguage_: function(langCode) {
376 langCode = this.convertLangCodeForTranslation_(langCode);
377 if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
378 this.translateBlockedLanguages_.push(langCode);
379 Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
380 this.translateBlockedLanguages_, true);
385 * Removes a language from the preference 'translate_blocked_languages'.
386 * If |langCode| doesn't exist in the preference, nothing happens.
387 * |langCode| is converted to a Translate language synonym before removed.
388 * @param {string} langCode A language code like 'en'
391 removeBlockedLanguage_: function(langCode) {
392 langCode = this.convertLangCodeForTranslation_(langCode);
393 if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
394 this.translateBlockedLanguages_ =
395 this.translateBlockedLanguages_.filter(
396 function(langCodeNotTranslated) {
397 return langCodeNotTranslated != langCode;
399 Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
400 this.translateBlockedLanguages_, true);
405 * Handles Page's visible property change event.
406 * @param {Event} e Property change event.
409 handleVisibleChange_: function(e) {
411 $('language-options-list').redraw();
412 chrome.send('languageOptionsOpen');
417 * Handles languageOptionsList's change event.
418 * @param {Event} e Change event.
421 handleLanguageOptionsListChange_: function(e) {
422 var languageOptionsList = $('language-options-list');
423 var languageCode = languageOptionsList.getSelectedLanguageCode();
425 // If there's no selection, just return.
429 // Select the language if it's specified in the URL hash (ex. lang=ja).
430 // Used for automated testing.
431 var match = document.location.hash.match(/\blang=([\w-]+)/);
433 var specifiedLanguageCode = match[1];
434 if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
435 languageCode = specifiedLanguageCode;
439 this.updateOfferToTranslateCheckbox_(languageCode);
441 if (cr.isWindows || cr.isChromeOS)
442 this.updateUiLanguageButton_(languageCode);
444 this.updateSelectedLanguageName_(languageCode);
447 this.updateSpellCheckLanguageControls_(languageCode);
450 this.updateInputMethodList_(languageCode);
452 this.updateLanguageListInAddLanguageOverlay_();
456 * Handles languageOptionsList's save event.
457 * @param {Event} e Save event.
460 handleLanguageOptionsListSave_: function(e) {
462 // Sort the preload engines per the saved languages before save.
463 this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
464 this.savePreloadEnginesPref_();
469 * Sorts preloadEngines_ by languageOptionsList's order.
470 * @param {Array} preloadEngines List of preload engines.
471 * @return {Array} Returns sorted preloadEngines.
474 sortPreloadEngines_: function(preloadEngines) {
475 // For instance, suppose we have two languages and associated input
481 // The preloadEngines preference should look like "hangul,pinyin".
482 // If the user reverse the order, the preference should be reorderd
483 // to "pinyin,hangul".
484 var languageOptionsList = $('language-options-list');
485 var languageCodes = languageOptionsList.getLanguageCodes();
487 // Convert the list into a dictonary for simpler lookup.
488 var preloadEngineSet = {};
489 for (var i = 0; i < preloadEngines.length; i++) {
490 preloadEngineSet[preloadEngines[i]] = true;
493 // Create the new preload engine list per the language codes.
494 var newPreloadEngines = [];
495 for (var i = 0; i < languageCodes.length; i++) {
496 var languageCode = languageCodes[i];
497 var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
502 // Check if we have active input methods associated with the language.
503 for (var j = 0; j < inputMethodIds.length; j++) {
504 var inputMethodId = inputMethodIds[j];
505 if (inputMethodId in preloadEngineSet) {
506 // If we have, add it to the new engine list.
507 newPreloadEngines.push(inputMethodId);
508 // And delete it from the set. This is necessary as one input
509 // method can be associated with more than one language thus
510 // we should avoid having duplicates in the new list.
511 delete preloadEngineSet[inputMethodId];
516 return newPreloadEngines;
520 * Initializes the map of language code to input method IDs.
523 initializeLanguageCodeToInputMethodIdsMap_: function() {
524 var inputMethodList = loadTimeData.getValue('inputMethodList');
525 for (var i = 0; i < inputMethodList.length; i++) {
526 var inputMethod = inputMethodList[i];
527 for (var languageCode in inputMethod.languageCodeSet) {
528 if (languageCode in this.languageCodeToInputMethodIdsMap_) {
529 this.languageCodeToInputMethodIdsMap_[languageCode].push(
532 this.languageCodeToInputMethodIdsMap_[languageCode] =
540 * Updates the currently selected language name.
541 * @param {string} languageCode Language code (ex. "fr").
544 updateSelectedLanguageName_: function(languageCode) {
545 var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
547 var languageDisplayName = languageInfo.displayName;
548 var languageNativeDisplayName = languageInfo.nativeDisplayName;
549 var textDirection = languageInfo.textDirection;
551 // If the native name is different, add it.
552 if (languageDisplayName != languageNativeDisplayName) {
553 languageDisplayName += ' - ' + languageNativeDisplayName;
556 // Update the currently selected language name.
557 var languageName = $('language-options-language-name');
558 languageName.textContent = languageDisplayName;
559 languageName.dir = textDirection;
563 * Updates the UI language button.
564 * @param {string} languageCode Language code (ex. "fr").
567 updateUiLanguageButton_: function(languageCode) {
568 var uiLanguageButton = $('language-options-ui-language-button');
569 var uiLanguageMessage = $('language-options-ui-language-message');
570 var uiLanguageNotification = $('language-options-ui-notification-bar');
572 // Remove the event listener and add it back if useful.
573 uiLanguageButton.onclick = null;
575 // Unhide the language button every time, as it could've been previously
576 // hidden by a language change.
577 uiLanguageButton.hidden = false;
579 // Hide the controlled setting indicator.
580 var uiLanguageIndicator = document.querySelector(
581 '.language-options-contents .controlled-setting-indicator');
582 uiLanguageIndicator.removeAttribute('controlled-by');
584 if (languageCode == this.prospectiveUiLanguageCode_) {
585 uiLanguageMessage.textContent =
586 loadTimeData.getString('isDisplayedInThisLanguage');
587 showMutuallyExclusiveNodes(
588 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
589 } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
590 if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
591 // In the guest mode for ChromeOS, changing UI language does not make
592 // sense because it does not take effect after browser restart.
593 uiLanguageButton.hidden = true;
594 uiLanguageMessage.hidden = true;
596 uiLanguageButton.textContent =
597 loadTimeData.getString('displayInThisLanguage');
599 if (loadTimeData.valueExists('secondaryUser') &&
600 loadTimeData.getBoolean('secondaryUser')) {
601 uiLanguageButton.disabled = true;
602 uiLanguageIndicator.setAttribute('controlled-by', 'shared');
604 uiLanguageButton.onclick = function(e) {
605 chrome.send('uiLanguageChange', [languageCode]);
608 showMutuallyExclusiveNodes(
609 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
612 uiLanguageMessage.textContent =
613 loadTimeData.getString('cannotBeDisplayedInThisLanguage');
614 showMutuallyExclusiveNodes(
615 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
620 * Updates the spell check language button/checkbox, dictionary download
621 * dialog, and the "Enable spell checking" checkbox.
622 * @param {string} languageCode Language code (ex. "fr").
625 updateSpellCheckLanguageControls_: function(languageCode) {
626 assert(languageCode);
627 var spellCheckLanguageSection = $('language-options-spellcheck');
628 var spellCheckLanguageButton = $('spellcheck-language-button');
629 var spellCheckLanguageCheckboxContainer =
630 $('spellcheck-language-checkbox-container');
631 var spellCheckLanguageCheckbox = $('spellcheck-language-checkbox');
632 var spellCheckLanguageMessage = $('spellcheck-language-message');
633 var dictionaryDownloadInProgress =
634 $('language-options-dictionary-downloading-message');
635 var dictionaryDownloadFailed =
636 $('language-options-dictionary-download-failed-message');
637 var dictionaryDownloadFailHelp =
638 $('language-options-dictionary-download-fail-help-message');
640 spellCheckLanguageSection.hidden = false;
641 spellCheckLanguageMessage.hidden = true;
642 spellCheckLanguageButton.hidden = true;
643 spellCheckLanguageCheckboxContainer.hidden = true;
644 dictionaryDownloadInProgress.hidden = true;
645 dictionaryDownloadFailed.hidden = true;
646 dictionaryDownloadFailHelp.hidden = true;
647 spellCheckLanguageCheckbox.checked = false;
649 var canBeUsedForSpellchecking =
650 languageCode in loadTimeData.getValue('spellCheckLanguageCodeSet');
652 if (!canBeUsedForSpellchecking) {
653 spellCheckLanguageMessage.textContent =
654 loadTimeData.getString('cannotBeUsedForSpellChecking');
655 spellCheckLanguageMessage.hidden = false;
659 var isUsedForSpellchecking = languageCode in this.spellCheckLanguages_;
660 var isLanguageDownloaded =
661 !(languageCode in this.spellcheckDictionaryDownloadStatus_);
663 if (this.isMultilingualSpellcheckerEnabled_()) {
664 spellCheckLanguageCheckbox.languageCode = languageCode;
665 spellCheckLanguageCheckbox.checked = isUsedForSpellchecking;
666 spellCheckLanguageCheckboxContainer.hidden = false;
667 } else if (isUsedForSpellchecking) {
668 if (isLanguageDownloaded) {
669 spellCheckLanguageMessage.textContent =
670 loadTimeData.getString('isUsedForSpellChecking');
671 spellCheckLanguageMessage.hidden = false;
674 spellCheckLanguageButton.textContent =
675 loadTimeData.getString('useThisForSpellChecking');
676 spellCheckLanguageButton.hidden = false;
677 spellCheckLanguageButton.languageCode = languageCode;
680 switch (this.spellcheckDictionaryDownloadStatus_[languageCode]) {
681 case DOWNLOAD_STATUS.IN_PROGRESS:
682 dictionaryDownloadInProgress.hidden = false;
684 case DOWNLOAD_STATUS.FAILED:
685 showMutuallyExclusiveNodes(
686 [spellCheckLanguageSection, dictionaryDownloadFailed], 1);
687 if (this.spellcheckDictionaryDownloadFailures_ > 1)
688 dictionaryDownloadFailHelp.hidden = false;
689 $('dictionary-download-retry-button').languageCode = languageCode;
693 var areNoLanguagesSelected =
694 Object.keys(this.spellCheckLanguages_).length == 0;
695 var usesSystemSpellchecker = !$('enable-spellcheck-container');
696 var isSpellcheckingEnabled = usesSystemSpellchecker ||
697 this.isMultilingualSpellcheckerEnabled_() ||
698 $('enable-spellcheck').checked;
699 $('edit-custom-dictionary-button').hidden =
700 areNoLanguagesSelected || !isSpellcheckingEnabled;
704 * Updates the checkbox for stopping translation.
705 * @param {string} languageCode Language code (ex. "fr").
708 updateOfferToTranslateCheckbox_: function(languageCode) {
709 var div = $('language-options-offer-to-translate');
711 // Translation server supports Chinese (Transitional) and Chinese
712 // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
713 // show this preference when general Chinese is selected.
714 if (languageCode != 'zh') {
721 var offerToTranslate = div.querySelector('div');
722 var cannotTranslate = $('cannot-translate-in-this-language');
723 var nodes = [offerToTranslate, cannotTranslate];
725 var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
726 if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
727 showMutuallyExclusiveNodes(nodes, 0);
729 showMutuallyExclusiveNodes(nodes, 1);
733 var checkbox = $('offer-to-translate-in-this-language');
735 if (!this.enableTranslate_) {
736 checkbox.disabled = true;
737 checkbox.checked = false;
741 // If the language corresponds to the default target language (in most
742 // cases, the user's locale language), "Offer to translate" checkbox
743 // should be always unchecked.
744 var defaultTargetLanguage =
745 loadTimeData.getString('defaultTargetLanguage');
746 if (convertedLangCode == defaultTargetLanguage) {
747 checkbox.disabled = true;
748 checkbox.checked = false;
752 checkbox.disabled = false;
754 var blockedLanguages = this.translateBlockedLanguages_;
755 var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
756 checkbox.checked = checked;
760 * Updates the input method list.
761 * @param {string} languageCode Language code (ex. "fr").
764 updateInputMethodList_: function(languageCode) {
765 // Give one of the checkboxes or buttons focus, if it's specified in the
766 // URL hash (ex. focus=mozc). Used for automated testing.
767 var focusInputMethodId = -1;
768 var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
770 focusInputMethodId = match[1];
772 // Change the visibility of the input method list. Input methods that
773 // matches |languageCode| will become visible.
774 var inputMethodList = $('language-options-input-method-list');
775 var methods = inputMethodList.querySelectorAll('.input-method');
776 for (var i = 0; i < methods.length; i++) {
777 var method = methods[i];
778 if (languageCode in method.languageCodeSet) {
779 method.hidden = false;
780 var input = method.querySelector('input');
781 // Give it focus if the ID matches.
782 if (input.inputMethodId == focusInputMethodId) {
786 method.hidden = true;
790 $('language-options-input-method-none').hidden =
791 (languageCode in this.languageCodeToInputMethodIdsMap_);
793 if (focusInputMethodId == 'add') {
794 $('language-options-add-button').focus();
799 * Updates the language list in the add language overlay.
802 updateLanguageListInAddLanguageOverlay_: function() {
803 // Change the visibility of the language list in the add language
804 // overlay. Languages that are already active will become invisible,
805 // so that users don't add the same language twice.
806 var languageOptionsList = $('language-options-list');
807 var languageCodes = languageOptionsList.getLanguageCodes();
808 var languageCodeSet = {};
809 for (var i = 0; i < languageCodes.length; i++) {
810 languageCodeSet[languageCodes[i]] = true;
813 var addLanguageList = $('add-language-overlay-language-list');
814 var options = addLanguageList.querySelectorAll('option');
815 assert(options.length > 0);
816 var selectedFirstItem = false;
817 for (var i = 0; i < options.length; i++) {
818 var option = options[i];
819 option.hidden = option.value in languageCodeSet;
820 if (!option.hidden && !selectedFirstItem) {
821 // Select first visible item, otherwise previously selected hidden
822 // item will be selected by default at the next time.
823 option.selected = true;
824 selectedFirstItem = true;
830 * Handles preloadEnginesPref change.
831 * @param {Event} e Change event.
834 handlePreloadEnginesPrefChange_: function(e) {
835 var value = e.value.value;
836 this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
837 this.updateCheckboxesFromPreloadEngines_();
838 $('language-options-list').updateDeletable();
842 * Handles enabledExtensionImePref change.
843 * @param {Event} e Change event.
846 handleEnabledExtensionsPrefChange_: function(e) {
847 var value = e.value.value;
848 this.enabledExtensionImes_ = value.split(',');
849 this.updateCheckboxesFromEnabledExtensions_();
853 * Handles offer-to-translate checkbox's click event.
854 * @param {Event} e Click event.
857 handleOfferToTranslateCheckboxClick_: function(e) {
858 var checkbox = e.target;
859 var checked = checkbox.checked;
861 var languageOptionsList = $('language-options-list');
862 var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
865 this.removeBlockedLanguage_(selectedLanguageCode);
867 this.addBlockedLanguage_(selectedLanguageCode);
871 * Handles input method checkbox's click event.
872 * @param {Event} e Click event.
875 handleCheckboxClick_: function(e) {
876 var checkbox = assertInstanceof(e.target, Element);
878 // Third party IMEs require additional confirmation prior to enabling due
880 if (/^_ext_ime_/.test(checkbox.inputMethodId) && checkbox.checked) {
881 var confirmationCallback = this.handleCheckboxUpdate_.bind(this,
883 var cancellationCallback = function() {
884 checkbox.checked = false;
886 ThirdPartyImeConfirmOverlay.showConfirmationDialog({
887 extension: checkbox.imeProvider,
888 confirm: confirmationCallback,
889 cancel: cancellationCallback
892 this.handleCheckboxUpdate_(checkbox);
895 chrome.send('coreOptionsUserMetricsAction',
896 ['Options_Languages_InputMethodCheckbox' +
897 (checkbox.checked ? '_Enable' : '_Disable')]);
901 * Updates active IMEs based on change in state of a checkbox for an input
903 * @param {!Element} checkbox Updated checkbox element.
906 handleCheckboxUpdate_: function(checkbox) {
907 if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
908 this.updateEnabledExtensionsFromCheckboxes_();
909 this.saveEnabledExtensionPref_();
912 if (this.preloadEngines_.length == 1 && !checkbox.checked) {
913 // Don't allow disabling the last input method.
914 this.showNotification_(
915 loadTimeData.getString('pleaseAddAnotherInputMethod'),
916 loadTimeData.getString('okButton'));
917 checkbox.checked = true;
920 if (checkbox.checked) {
921 chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
923 chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
925 this.updatePreloadEnginesFromCheckboxes_();
926 this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
927 this.savePreloadEnginesPref_();
931 * Handles clicks on the "OK" button of the "Add language" dialog.
932 * @param {Event} e Click event.
935 handleAddLanguageOkButtonClick_: function(e) {
936 var languagesSelect = $('add-language-overlay-language-list');
937 var selectedIndex = languagesSelect.selectedIndex;
938 if (selectedIndex >= 0) {
939 var selection = languagesSelect.options[selectedIndex];
940 var langCode = String(selection.value);
941 $('language-options-list').addLanguage(langCode);
942 this.addBlockedLanguage_(langCode);
943 PageManager.closeOverlay();
948 * Checks if languageCode is deletable or not.
949 * @param {string} languageCode the languageCode to check for deletability.
951 languageIsDeletable: function(languageCode) {
952 // Don't allow removing the language if it's a UI language.
953 if (languageCode == this.prospectiveUiLanguageCode_)
955 return (!cr.isChromeOS ||
956 this.canDeleteLanguage_(languageCode));
960 * Handles browse.enable_spellchecking change.
961 * @param {Event} e Change event.
964 updateEnableSpellCheck_: function(e) {
965 var value = !$('enable-spellcheck').checked;
966 var languageControl = $(this.isMultilingualSpellcheckerEnabled_() ?
967 'spellcheck-language-checkbox' : 'spellcheck-language-button');
968 languageControl.disabled = value;
970 $('edit-custom-dictionary-button').hidden = value;
974 * Handles translateBlockedLanguagesPref change.
975 * @param {Event} e Change event.
978 handleTranslateBlockedLanguagesPrefChange_: function(e) {
979 this.translateBlockedLanguages_ = e.value.value;
980 this.updateOfferToTranslateCheckbox_(
981 $('language-options-list').getSelectedLanguageCode());
985 * Updates spellcheck dictionary UI (checkboxes, buttons, and labels) when
986 * preferences change.
987 * @param {Event} e Preference change event where e.value.value is the list
988 * of languages currently used for spellchecking.
991 handleSpellCheckDictionariesPrefChange_: function(e) {
995 var languages = e.value.value;
996 this.spellCheckLanguages_ = {};
997 for (var i = 0; i < languages.length; i++) {
998 this.spellCheckLanguages_[languages[i]] = true;
1000 this.updateSpellCheckLanguageControls_(
1001 $('language-options-list').getSelectedLanguageCode());
1005 * Handles translate.enabled change.
1006 * @param {Event} e Change event.
1009 handleEnableTranslatePrefChange_: function(e) {
1010 var enabled = e.value.value;
1011 this.enableTranslate_ = enabled;
1012 this.updateOfferToTranslateCheckbox_(
1013 $('language-options-list').getSelectedLanguageCode());
1017 * Handles spellCheckLanguageButton click.
1018 * @param {Event} e Click event.
1021 handleSpellCheckLanguageButtonClick_: function(e) {
1022 var languageCode = e.currentTarget.languageCode;
1023 // Save the preference.
1024 Preferences.setListPref(SPELL_CHECK_DICTIONARIES_PREF,
1025 [languageCode], true);
1027 // The spellCheckLanguageChange argument is only used for logging.
1028 chrome.send('spellCheckLanguageChange', [languageCode]);
1029 chrome.send('coreOptionsUserMetricsAction',
1030 ['Options_Languages_SpellCheck']);
1034 * Updates the spellcheck.dictionaries preference with the currently
1035 * selected language codes.
1036 * @param {Event} e Click event. e.currentTarget represents the "Use this
1037 * language for spellchecking" checkbox.
1040 handleSpellCheckLanguageCheckboxClick_: function(e) {
1041 var languageCode = e.currentTarget.languageCode;
1043 if (e.currentTarget.checked)
1044 this.spellCheckLanguages_[languageCode] = true;
1046 delete this.spellCheckLanguages_[languageCode];
1048 var languageCodes = Object.keys(this.spellCheckLanguages_);
1049 Preferences.setListPref(SPELL_CHECK_DICTIONARIES_PREF,
1050 languageCodes, true);
1052 // The spellCheckLanguageChange argument is only used for logging.
1053 chrome.send('spellCheckLanguageChange', [languageCodes.join(',')]);
1054 chrome.send('coreOptionsUserMetricsAction',
1055 ['Options_Languages_SpellCheck']);
1059 * Checks whether it's possible to remove the language specified by
1060 * languageCode and returns true if possible. This function returns false
1061 * if the removal causes the number of preload engines to be zero.
1063 * @param {string} languageCode Language code (ex. "fr").
1064 * @return {boolean} Returns true on success.
1067 canDeleteLanguage_: function(languageCode) {
1068 // First create the set of engines to be removed from input methods
1069 // associated with the language code.
1070 var enginesToBeRemovedSet = {};
1071 var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
1073 // If this language doesn't have any input methods, it can be deleted.
1074 if (!inputMethodIds)
1077 for (var i = 0; i < inputMethodIds.length; i++) {
1078 enginesToBeRemovedSet[inputMethodIds[i]] = true;
1081 // Then eliminate engines that are also used for other active languages.
1082 // For instance, if "xkb:us::eng" is used for both English and Filipino.
1083 var languageCodes = $('language-options-list').getLanguageCodes();
1084 for (var i = 0; i < languageCodes.length; i++) {
1085 // Skip the target language code.
1086 if (languageCodes[i] == languageCode) {
1089 // Check if input methods used in this language are included in
1090 // enginesToBeRemovedSet. If so, eliminate these from the set, so
1091 // we don't remove this time.
1092 var inputMethodIdsForAnotherLanguage =
1093 this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
1094 if (!inputMethodIdsForAnotherLanguage)
1097 for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
1098 var inputMethodId = inputMethodIdsForAnotherLanguage[j];
1099 if (inputMethodId in enginesToBeRemovedSet) {
1100 delete enginesToBeRemovedSet[inputMethodId];
1105 // Update the preload engine list with the to-be-removed set.
1106 var newPreloadEngines = [];
1107 for (var i = 0; i < this.preloadEngines_.length; i++) {
1108 if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
1109 newPreloadEngines.push(this.preloadEngines_[i]);
1112 // Don't allow this operation if it causes the number of preload
1113 // engines to be zero.
1114 return (newPreloadEngines.length > 0);
1118 * Saves the enabled extension preference.
1121 saveEnabledExtensionPref_: function() {
1122 Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
1123 this.enabledExtensionImes_.join(','), true);
1127 * Updates the checkboxes in the input method list from the enabled
1128 * extensions preference.
1131 updateCheckboxesFromEnabledExtensions_: function() {
1132 // Convert the list into a dictonary for simpler lookup.
1133 var dictionary = {};
1134 for (var i = 0; i < this.enabledExtensionImes_.length; i++)
1135 dictionary[this.enabledExtensionImes_[i]] = true;
1137 var inputMethodList = $('language-options-input-method-list');
1138 var checkboxes = inputMethodList.querySelectorAll('input');
1139 for (var i = 0; i < checkboxes.length; i++) {
1140 if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1141 checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1143 var configureButtons = inputMethodList.querySelectorAll('button');
1144 for (var i = 0; i < configureButtons.length; i++) {
1145 if (configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1146 configureButtons[i].hidden =
1147 !(configureButtons[i].inputMethodId in dictionary);
1153 * Updates the enabled extensions preference from the checkboxes in the
1154 * input method list.
1157 updateEnabledExtensionsFromCheckboxes_: function() {
1158 this.enabledExtensionImes_ = [];
1159 var inputMethodList = $('language-options-input-method-list');
1160 var checkboxes = inputMethodList.querySelectorAll('input');
1161 for (var i = 0; i < checkboxes.length; i++) {
1162 if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1163 if (checkboxes[i].checked)
1164 this.enabledExtensionImes_.push(checkboxes[i].inputMethodId);
1170 * Saves the preload engines preference.
1173 savePreloadEnginesPref_: function() {
1174 Preferences.setStringPref(PRELOAD_ENGINES_PREF,
1175 this.preloadEngines_.join(','), true);
1179 * Updates the checkboxes in the input method list from the preload
1180 * engines preference.
1183 updateCheckboxesFromPreloadEngines_: function() {
1184 // Convert the list into a dictonary for simpler lookup.
1185 var dictionary = {};
1186 for (var i = 0; i < this.preloadEngines_.length; i++) {
1187 dictionary[this.preloadEngines_[i]] = true;
1190 var inputMethodList = $('language-options-input-method-list');
1191 var checkboxes = inputMethodList.querySelectorAll('input');
1192 for (var i = 0; i < checkboxes.length; i++) {
1193 if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1194 checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1196 var configureButtons = inputMethodList.querySelectorAll('button');
1197 for (var i = 0; i < configureButtons.length; i++) {
1198 if (!configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1199 configureButtons[i].hidden =
1200 !(configureButtons[i].inputMethodId in dictionary);
1206 * Updates the preload engines preference from the checkboxes in the
1207 * input method list.
1210 updatePreloadEnginesFromCheckboxes_: function() {
1211 this.preloadEngines_ = [];
1212 var inputMethodList = $('language-options-input-method-list');
1213 var checkboxes = inputMethodList.querySelectorAll('input');
1214 for (var i = 0; i < checkboxes.length; i++) {
1215 if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1216 if (checkboxes[i].checked)
1217 this.preloadEngines_.push(checkboxes[i].inputMethodId);
1220 var languageOptionsList = $('language-options-list');
1221 languageOptionsList.updateDeletable();
1225 * Filters bad preload engines in case bad preload engines are
1226 * stored in the preference. Removes duplicates as well.
1227 * @param {Array} preloadEngines List of preload engines.
1230 filterBadPreloadEngines_: function(preloadEngines) {
1231 // Convert the list into a dictonary for simpler lookup.
1232 var dictionary = {};
1233 var list = loadTimeData.getValue('inputMethodList');
1234 for (var i = 0; i < list.length; i++) {
1235 dictionary[list[i].id] = true;
1238 var enabledPreloadEngines = [];
1240 for (var i = 0; i < preloadEngines.length; i++) {
1241 // Check if the preload engine is present in the
1242 // dictionary, and not duplicate. Otherwise, skip it.
1243 // Component Extension IME should be handled same as preloadEngines and
1244 // "_comp_" is the special prefix of its ID.
1245 if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) ||
1246 /^_comp_/.test(preloadEngines[i])) {
1247 enabledPreloadEngines.push(preloadEngines[i]);
1248 seen[preloadEngines[i]] = true;
1251 return enabledPreloadEngines;
1254 // TODO(kochi): This is an adapted copy from new_tab.js.
1255 // If this will go as final UI, refactor this to share the component with
1256 // new new tab page.
1260 notificationTimeout_: null,
1263 * Shows notification.
1264 * @param {string} text
1265 * @param {string} actionText
1266 * @param {number=} opt_delay
1269 showNotification_: function(text, actionText, opt_delay) {
1270 var notificationElement = $('notification');
1271 var actionLink = notificationElement.querySelector('.link-color');
1272 var delay = opt_delay || 10000;
1275 window.clearTimeout(this.notificationTimeout_);
1276 notificationElement.classList.add('show');
1277 document.body.classList.add('notification-shown');
1281 window.clearTimeout(this.notificationTimeout_);
1282 notificationElement.classList.remove('show');
1283 document.body.classList.remove('notification-shown');
1284 // Prevent tabbing to the hidden link.
1285 actionLink.tabIndex = -1;
1286 // Setting tabIndex to -1 only prevents future tabbing to it. If,
1287 // however, the user switches window or a tab and then moves back to
1288 // this tab the element may gain focus. We therefore make sure that we
1289 // blur the element so that the element focus is not restored when
1290 // coming back to this window.
1294 function delayedHide() {
1295 this.notificationTimeout_ = window.setTimeout(hide, delay);
1298 notificationElement.firstElementChild.textContent = text;
1299 actionLink.textContent = actionText;
1301 actionLink.onclick = hide;
1302 actionLink.onkeydown = function(e) {
1303 if (e.keyIdentifier == 'Enter') {
1307 notificationElement.onmouseover = show;
1308 notificationElement.onmouseout = delayedHide;
1309 actionLink.onfocus = show;
1310 actionLink.onblur = delayedHide;
1311 // Enable tabbing to the link now that it is shown.
1312 actionLink.tabIndex = 0;
1319 * Chrome callback for when the UI language preference is saved.
1320 * @param {string} languageCode The newly selected language to use.
1323 uiLanguageSaved_: function(languageCode) {
1324 this.prospectiveUiLanguageCode_ = languageCode;
1326 // If the user is no longer on the same language code, ignore.
1327 if ($('language-options-list').getSelectedLanguageCode() != languageCode)
1330 // Special case for when a user changes to a different language, and
1331 // changes back to the same language without having restarted Chrome or
1332 // logged in/out of ChromeOS.
1333 if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
1334 this.updateUiLanguageButton_(languageCode);
1338 // Otherwise, show a notification telling the user that their changes will
1339 // only take effect after restart.
1340 showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
1341 $('language-options-ui-notification-bar')],
1346 * A handler for when dictionary for |languageCode| begins downloading.
1347 * @param {string} languageCode The language of the dictionary that just
1348 * began downloading.
1351 onDictionaryDownloadBegin_: function(languageCode) {
1352 this.spellcheckDictionaryDownloadStatus_[languageCode] =
1353 DOWNLOAD_STATUS.IN_PROGRESS;
1356 $('language-options-list').getSelectedLanguageCode()) {
1357 this.updateSpellCheckLanguageControls_(languageCode);
1362 * A handler for when dictionary for |languageCode| successfully downloaded.
1363 * @param {string} languageCode The language of the dictionary that
1364 * succeeded downloading.
1367 onDictionaryDownloadSuccess_: function(languageCode) {
1368 delete this.spellcheckDictionaryDownloadStatus_[languageCode];
1369 this.spellcheckDictionaryDownloadFailures_ = 0;
1372 $('language-options-list').getSelectedLanguageCode()) {
1373 this.updateSpellCheckLanguageControls_(languageCode);
1378 * A handler for when dictionary for |languageCode| fails to download.
1379 * @param {string} languageCode The language of the dictionary that failed
1383 onDictionaryDownloadFailure_: function(languageCode) {
1384 this.spellcheckDictionaryDownloadStatus_[languageCode] =
1385 DOWNLOAD_STATUS.FAILED;
1386 this.spellcheckDictionaryDownloadFailures_++;
1389 $('language-options-list').getSelectedLanguageCode()) {
1390 this.updateSpellCheckLanguageControls_(languageCode);
1395 * Converts the language code for Translation. There are some differences
1396 * between the language set for Translation and that for Accept-Language.
1397 * @param {string} languageCode The language code like 'fr'.
1398 * @return {string} The converted language code.
1401 convertLangCodeForTranslation_: function(languageCode) {
1402 var tokens = languageCode.split('-');
1403 var main = tokens[0];
1405 // See also: components/translate/core/browser/common/translate_util.cc
1416 if (main in synonyms) {
1417 return synonyms[main];
1418 } else if (main == 'zh') {
1419 // In Translation, general Chinese is not used, and the sub code is
1420 // necessary as a language code for Translate server.
1421 return languageCode;
1429 * Shows the node at |index| in |nodes|, hides all others.
1430 * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
1431 * @param {number} index The index of |nodes| to show.
1433 function showMutuallyExclusiveNodes(nodes, index) {
1434 assert(index >= 0 && index < nodes.length);
1435 for (var i = 0; i < nodes.length; ++i) {
1436 assert(nodes[i] instanceof HTMLElement); // TODO(dbeam): Ignore null?
1437 nodes[i].hidden = i != index;
1441 LanguageOptions.uiLanguageSaved = function(languageCode) {
1442 LanguageOptions.getInstance().uiLanguageSaved_(languageCode);
1445 LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
1446 LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
1449 LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
1450 LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
1453 LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
1454 LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
1459 LanguageOptions: LanguageOptions