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 string that describes the spell check
56 * dictionary language, like "en-US".
60 var SPELL_CHECK_DICTIONARY_PREF = 'spellcheck.dictionary';
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 preference is a string that describes the spell check dictionary
140 * language, like "en-US".
144 spellCheckDictionary_: '',
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 initializePage: function() {
163 Page.prototype.initializePage.call(this);
165 var languageOptionsList = $('language-options-list');
166 LanguageList.decorate(languageOptionsList);
168 languageOptionsList.addEventListener('change',
169 this.handleLanguageOptionsListChange_.bind(this));
170 languageOptionsList.addEventListener('save',
171 this.handleLanguageOptionsListSave_.bind(this));
173 this.prospectiveUiLanguageCode_ =
174 loadTimeData.getString('prospectiveUiLanguageCode');
175 this.addEventListener('visibleChange',
176 this.handleVisibleChange_.bind(this));
179 this.initializeInputMethodList_();
180 this.initializeLanguageCodeToInputMethodIdsMap_();
183 var checkbox = $('offer-to-translate-in-this-language');
184 checkbox.addEventListener('click',
185 this.handleOfferToTranslateCheckboxClick_.bind(this));
187 Preferences.getInstance().addEventListener(
188 TRANSLATE_BLOCKED_LANGUAGES_PREF,
189 this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
190 Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARY_PREF,
191 this.handleSpellCheckDictionaryPrefChange_.bind(this));
192 Preferences.getInstance().addEventListener(ENABLE_TRANSLATE,
193 this.handleEnableTranslatePrefChange_.bind(this));
194 this.translateSupportedLanguages_ =
195 loadTimeData.getValue('translateSupportedLanguages');
197 // Set up add button.
198 var onclick = function(e) {
199 // Add the language without showing the overlay if it's specified in
200 // the URL hash (ex. lang_add=ja). Used for automated testing.
201 var match = document.location.hash.match(/\blang_add=([\w-]+)/);
203 var addLanguageCode = match[1];
204 $('language-options-list').addLanguage(addLanguageCode);
205 this.addBlockedLanguage_(addLanguageCode);
207 PageManager.showPageByName('addLanguage');
208 chrome.send('coreOptionsUserMetricsAction',
209 ['Options_Languages_Add']);
212 $('language-options-add-button').onclick = onclick.bind(this);
215 // Set up the button for editing custom spelling dictionary.
216 $('edit-dictionary-button').onclick = function(e) {
217 PageManager.showPageByName('editDictionary');
219 $('dictionary-download-retry-button').onclick = function(e) {
220 chrome.send('retryDictionaryDownload');
224 // Listen to add language dialog ok button.
225 $('add-language-overlay-ok-button').addEventListener(
226 'click', this.handleAddLanguageOkButtonClick_.bind(this));
228 if (!cr.isChromeOS) {
229 // Show experimental features if enabled.
230 if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
231 $('auto-spell-correction-option').hidden = false;
233 // Handle spell check enable/disable.
235 Preferences.getInstance().addEventListener(
236 ENABLE_SPELL_CHECK_PREF,
237 this.updateEnableSpellCheck_.bind(this));
241 // Handle clicks on "Use this language for spell checking" button.
243 var spellCheckLanguageButton = getRequiredElement(
244 'language-options-spell-check-language-button');
245 spellCheckLanguageButton.addEventListener(
247 this.handleSpellCheckLanguageButtonClick_.bind(this));
251 $('language-options-ui-restart-button').onclick = function() {
252 chrome.send('uiLanguageRestart');
256 $('language-confirm').onclick =
257 PageManager.closeOverlay.bind(PageManager);
259 // Public session users cannot change the locale.
260 if (cr.isChromeOS && UIAccountTweaks.loggedInAsPublicAccount())
261 $('language-options-ui-language-section').hidden = true;
262 PageManager.closeOverlay.bind(PageManager);
266 * Initializes the input method list.
268 initializeInputMethodList_: function() {
269 var inputMethodList = $('language-options-input-method-list');
270 var inputMethodPrototype = $('language-options-input-method-template');
272 // Add all input methods, but make all of them invisible here. We'll
273 // change the visibility in handleLanguageOptionsListChange_() based
274 // on the selected language. Note that we only have less than 100
275 // input methods, so creating DOM nodes at once here should be ok.
276 this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
277 this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
278 this.appendComponentExtensionIme_(
279 loadTimeData.getValue('componentExtensionImeList'));
281 // Listen to pref change once the input method list is initialized.
282 Preferences.getInstance().addEventListener(
283 PRELOAD_ENGINES_PREF,
284 this.handlePreloadEnginesPrefChange_.bind(this));
285 Preferences.getInstance().addEventListener(
286 ENABLED_EXTENSION_IME_PREF,
287 this.handleEnabledExtensionsPrefChange_.bind(this));
291 * Appends input method lists based on component extension ime list.
292 * @param {!Array} componentExtensionImeList A list of input method
296 appendComponentExtensionIme_: function(componentExtensionImeList) {
297 this.appendInputMethodElement_(componentExtensionImeList);
299 for (var i = 0; i < componentExtensionImeList.length; i++) {
300 var inputMethod = componentExtensionImeList[i];
301 for (var languageCode in inputMethod.languageCodeSet) {
302 if (languageCode in this.languageCodeToInputMethodIdsMap_) {
303 this.languageCodeToInputMethodIdsMap_[languageCode].push(
306 this.languageCodeToInputMethodIdsMap_[languageCode] =
314 * Appends input methods into input method list.
315 * @param {!Array} inputMethods A list of input method descriptors.
318 appendInputMethodElement_: function(inputMethods) {
319 var inputMethodList = $('language-options-input-method-list');
320 var inputMethodTemplate = $('language-options-input-method-template');
322 for (var i = 0; i < inputMethods.length; i++) {
323 var inputMethod = inputMethods[i];
324 var element = inputMethodTemplate.cloneNode(true);
326 element.languageCodeSet = inputMethod.languageCodeSet;
328 var input = element.querySelector('input');
329 input.inputMethodId = inputMethod.id;
330 input.imeProvider = inputMethod.extensionName;
331 var span = element.querySelector('span');
332 span.textContent = inputMethod.displayName;
334 if (inputMethod.optionsPage) {
335 var button = document.createElement('button');
336 button.textContent = loadTimeData.getString('configure');
337 button.inputMethodId = inputMethod.id;
338 button.onclick = function(inputMethodId, e) {
339 chrome.send('inputMethodOptionsOpen', [inputMethodId]);
340 }.bind(this, inputMethod.id);
341 element.appendChild(button);
344 // Listen to user clicks.
345 input.addEventListener('click',
346 this.handleCheckboxClick_.bind(this));
347 inputMethodList.appendChild(element);
352 * Adds a language to the preference 'translate_blocked_languages'. If
353 * |langCode| is already added, nothing happens. |langCode| is converted
354 * to a Translate language synonym before added.
355 * @param {string} langCode A language code like 'en'
358 addBlockedLanguage_: function(langCode) {
359 langCode = this.convertLangCodeForTranslation_(langCode);
360 if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
361 this.translateBlockedLanguages_.push(langCode);
362 Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
363 this.translateBlockedLanguages_, true);
368 * Removes a language from the preference 'translate_blocked_languages'.
369 * If |langCode| doesn't exist in the preference, nothing happens.
370 * |langCode| is converted to a Translate language synonym before removed.
371 * @param {string} langCode A language code like 'en'
374 removeBlockedLanguage_: function(langCode) {
375 langCode = this.convertLangCodeForTranslation_(langCode);
376 if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
377 this.translateBlockedLanguages_ =
378 this.translateBlockedLanguages_.filter(
379 function(langCodeNotTranslated) {
380 return langCodeNotTranslated != langCode;
382 Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
383 this.translateBlockedLanguages_, true);
388 * Handles Page's visible property change event.
389 * @param {Event} e Property change event.
392 handleVisibleChange_: function(e) {
394 $('language-options-list').redraw();
395 chrome.send('languageOptionsOpen');
400 * Handles languageOptionsList's change event.
401 * @param {Event} e Change event.
404 handleLanguageOptionsListChange_: function(e) {
405 var languageOptionsList = $('language-options-list');
406 var languageCode = languageOptionsList.getSelectedLanguageCode();
408 // If there's no selection, just return.
412 // Select the language if it's specified in the URL hash (ex. lang=ja).
413 // Used for automated testing.
414 var match = document.location.hash.match(/\blang=([\w-]+)/);
416 var specifiedLanguageCode = match[1];
417 if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
418 languageCode = specifiedLanguageCode;
422 this.updateOfferToTranslateCheckbox_(languageCode);
424 if (cr.isWindows || cr.isChromeOS)
425 this.updateUiLanguageButton_(languageCode);
427 this.updateSelectedLanguageName_(languageCode);
430 this.updateSpellCheckLanguageButton_(languageCode);
433 this.updateInputMethodList_(languageCode);
435 this.updateLanguageListInAddLanguageOverlay_();
439 * Handles languageOptionsList's save event.
440 * @param {Event} e Save event.
443 handleLanguageOptionsListSave_: function(e) {
445 // Sort the preload engines per the saved languages before save.
446 this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
447 this.savePreloadEnginesPref_();
452 * Sorts preloadEngines_ by languageOptionsList's order.
453 * @param {Array} preloadEngines List of preload engines.
454 * @return {Array} Returns sorted preloadEngines.
457 sortPreloadEngines_: function(preloadEngines) {
458 // For instance, suppose we have two languages and associated input
464 // The preloadEngines preference should look like "hangul,pinyin".
465 // If the user reverse the order, the preference should be reorderd
466 // to "pinyin,hangul".
467 var languageOptionsList = $('language-options-list');
468 var languageCodes = languageOptionsList.getLanguageCodes();
470 // Convert the list into a dictonary for simpler lookup.
471 var preloadEngineSet = {};
472 for (var i = 0; i < preloadEngines.length; i++) {
473 preloadEngineSet[preloadEngines[i]] = true;
476 // Create the new preload engine list per the language codes.
477 var newPreloadEngines = [];
478 for (var i = 0; i < languageCodes.length; i++) {
479 var languageCode = languageCodes[i];
480 var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
485 // Check if we have active input methods associated with the language.
486 for (var j = 0; j < inputMethodIds.length; j++) {
487 var inputMethodId = inputMethodIds[j];
488 if (inputMethodId in preloadEngineSet) {
489 // If we have, add it to the new engine list.
490 newPreloadEngines.push(inputMethodId);
491 // And delete it from the set. This is necessary as one input
492 // method can be associated with more than one language thus
493 // we should avoid having duplicates in the new list.
494 delete preloadEngineSet[inputMethodId];
499 return newPreloadEngines;
503 * Initializes the map of language code to input method IDs.
506 initializeLanguageCodeToInputMethodIdsMap_: function() {
507 var inputMethodList = loadTimeData.getValue('inputMethodList');
508 for (var i = 0; i < inputMethodList.length; i++) {
509 var inputMethod = inputMethodList[i];
510 for (var languageCode in inputMethod.languageCodeSet) {
511 if (languageCode in this.languageCodeToInputMethodIdsMap_) {
512 this.languageCodeToInputMethodIdsMap_[languageCode].push(
515 this.languageCodeToInputMethodIdsMap_[languageCode] =
523 * Updates the currently selected language name.
524 * @param {string} languageCode Language code (ex. "fr").
527 updateSelectedLanguageName_: function(languageCode) {
528 var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
530 var languageDisplayName = languageInfo.displayName;
531 var languageNativeDisplayName = languageInfo.nativeDisplayName;
532 var textDirection = languageInfo.textDirection;
534 // If the native name is different, add it.
535 if (languageDisplayName != languageNativeDisplayName) {
536 languageDisplayName += ' - ' + languageNativeDisplayName;
539 // Update the currently selected language name.
540 var languageName = $('language-options-language-name');
541 languageName.textContent = languageDisplayName;
542 languageName.dir = textDirection;
546 * Updates the UI language button.
547 * @param {string} languageCode Language code (ex. "fr").
550 updateUiLanguageButton_: function(languageCode) {
551 var uiLanguageButton = $('language-options-ui-language-button');
552 var uiLanguageMessage = $('language-options-ui-language-message');
553 var uiLanguageNotification = $('language-options-ui-notification-bar');
555 // Remove the event listener and add it back if useful.
556 uiLanguageButton.onclick = null;
558 // Unhide the language button every time, as it could've been previously
559 // hidden by a language change.
560 uiLanguageButton.hidden = false;
562 // Hide the controlled setting indicator.
563 var uiLanguageIndicator = document.querySelector(
564 '.language-options-contents .controlled-setting-indicator');
565 uiLanguageIndicator.removeAttribute('controlled-by');
567 if (languageCode == this.prospectiveUiLanguageCode_) {
568 uiLanguageMessage.textContent =
569 loadTimeData.getString('isDisplayedInThisLanguage');
570 showMutuallyExclusiveNodes(
571 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
572 } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
573 if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
574 // In the guest mode for ChromeOS, changing UI language does not make
575 // sense because it does not take effect after browser restart.
576 uiLanguageButton.hidden = true;
577 uiLanguageMessage.hidden = true;
579 uiLanguageButton.textContent =
580 loadTimeData.getString('displayInThisLanguage');
582 if (loadTimeData.valueExists('secondaryUser') &&
583 loadTimeData.getBoolean('secondaryUser')) {
584 uiLanguageButton.disabled = true;
585 uiLanguageIndicator.setAttribute('controlled-by', 'shared');
587 uiLanguageButton.onclick = function(e) {
588 chrome.send('uiLanguageChange', [languageCode]);
591 showMutuallyExclusiveNodes(
592 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
595 uiLanguageMessage.textContent =
596 loadTimeData.getString('cannotBeDisplayedInThisLanguage');
597 showMutuallyExclusiveNodes(
598 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
603 * Updates the spell check language button.
604 * @param {string} languageCode Language code (ex. "fr").
607 updateSpellCheckLanguageButton_: function(languageCode) {
608 var spellCheckLanguageSection = $('language-options-spellcheck');
609 var spellCheckLanguageButton =
610 $('language-options-spell-check-language-button');
611 var spellCheckLanguageMessage =
612 $('language-options-spell-check-language-message');
613 var dictionaryDownloadInProgress =
614 $('language-options-dictionary-downloading-message');
615 var dictionaryDownloadFailed =
616 $('language-options-dictionary-download-failed-message');
617 var dictionaryDownloadFailHelp =
618 $('language-options-dictionary-download-fail-help-message');
619 spellCheckLanguageSection.hidden = false;
620 spellCheckLanguageMessage.hidden = true;
621 spellCheckLanguageButton.hidden = true;
622 dictionaryDownloadInProgress.hidden = true;
623 dictionaryDownloadFailed.hidden = true;
624 dictionaryDownloadFailHelp.hidden = true;
626 if (languageCode == this.spellCheckDictionary_) {
627 if (!(languageCode in this.spellcheckDictionaryDownloadStatus_)) {
628 spellCheckLanguageMessage.textContent =
629 loadTimeData.getString('isUsedForSpellChecking');
630 showMutuallyExclusiveNodes(
631 [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
632 } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
633 DOWNLOAD_STATUS.IN_PROGRESS) {
634 dictionaryDownloadInProgress.hidden = false;
635 } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
636 DOWNLOAD_STATUS.FAILED) {
637 spellCheckLanguageSection.hidden = true;
638 dictionaryDownloadFailed.hidden = false;
639 if (this.spellcheckDictionaryDownloadFailures_ > 1)
640 dictionaryDownloadFailHelp.hidden = false;
642 } else if (languageCode in
643 loadTimeData.getValue('spellCheckLanguageCodeSet')) {
644 spellCheckLanguageButton.textContent =
645 loadTimeData.getString('useThisForSpellChecking');
646 showMutuallyExclusiveNodes(
647 [spellCheckLanguageButton, spellCheckLanguageMessage], 0);
648 spellCheckLanguageButton.languageCode = languageCode;
649 } else if (!languageCode) {
650 spellCheckLanguageButton.hidden = true;
651 spellCheckLanguageMessage.hidden = true;
653 spellCheckLanguageMessage.textContent =
654 loadTimeData.getString('cannotBeUsedForSpellChecking');
655 showMutuallyExclusiveNodes(
656 [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
661 * Updates the checkbox for stopping translation.
662 * @param {string} languageCode Language code (ex. "fr").
665 updateOfferToTranslateCheckbox_: function(languageCode) {
666 var div = $('language-options-offer-to-translate');
668 // Translation server supports Chinese (Transitional) and Chinese
669 // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
670 // show this preference when general Chinese is selected.
671 if (languageCode != 'zh') {
678 var offerToTranslate = div.querySelector('div');
679 var cannotTranslate = $('cannot-translate-in-this-language');
680 var nodes = [offerToTranslate, cannotTranslate];
682 var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
683 if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
684 showMutuallyExclusiveNodes(nodes, 0);
686 showMutuallyExclusiveNodes(nodes, 1);
690 var checkbox = $('offer-to-translate-in-this-language');
692 if (!this.enableTranslate_) {
693 checkbox.disabled = true;
694 checkbox.checked = false;
698 // If the language corresponds to the default target language (in most
699 // cases, the user's locale language), "Offer to translate" checkbox
700 // should be always unchecked.
701 var defaultTargetLanguage =
702 loadTimeData.getString('defaultTargetLanguage');
703 if (convertedLangCode == defaultTargetLanguage) {
704 checkbox.disabled = true;
705 checkbox.checked = false;
709 checkbox.disabled = false;
711 var blockedLanguages = this.translateBlockedLanguages_;
712 var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
713 checkbox.checked = checked;
717 * Updates the input method list.
718 * @param {string} languageCode Language code (ex. "fr").
721 updateInputMethodList_: function(languageCode) {
722 // Give one of the checkboxes or buttons focus, if it's specified in the
723 // URL hash (ex. focus=mozc). Used for automated testing.
724 var focusInputMethodId = -1;
725 var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
727 focusInputMethodId = match[1];
729 // Change the visibility of the input method list. Input methods that
730 // matches |languageCode| will become visible.
731 var inputMethodList = $('language-options-input-method-list');
732 var methods = inputMethodList.querySelectorAll('.input-method');
733 for (var i = 0; i < methods.length; i++) {
734 var method = methods[i];
735 if (languageCode in method.languageCodeSet) {
736 method.hidden = false;
737 var input = method.querySelector('input');
738 // Give it focus if the ID matches.
739 if (input.inputMethodId == focusInputMethodId) {
743 method.hidden = true;
747 $('language-options-input-method-none').hidden =
748 (languageCode in this.languageCodeToInputMethodIdsMap_);
750 if (focusInputMethodId == 'add') {
751 $('language-options-add-button').focus();
756 * Updates the language list in the add language overlay.
759 updateLanguageListInAddLanguageOverlay_: function() {
760 // Change the visibility of the language list in the add language
761 // overlay. Languages that are already active will become invisible,
762 // so that users don't add the same language twice.
763 var languageOptionsList = $('language-options-list');
764 var languageCodes = languageOptionsList.getLanguageCodes();
765 var languageCodeSet = {};
766 for (var i = 0; i < languageCodes.length; i++) {
767 languageCodeSet[languageCodes[i]] = true;
770 var addLanguageList = $('add-language-overlay-language-list');
771 var options = addLanguageList.querySelectorAll('option');
772 assert(options.length > 0);
773 var selectedFirstItem = false;
774 for (var i = 0; i < options.length; i++) {
775 var option = options[i];
776 option.hidden = option.value in languageCodeSet;
777 if (!option.hidden && !selectedFirstItem) {
778 // Select first visible item, otherwise previously selected hidden
779 // item will be selected by default at the next time.
780 option.selected = true;
781 selectedFirstItem = true;
787 * Handles preloadEnginesPref change.
788 * @param {Event} e Change event.
791 handlePreloadEnginesPrefChange_: function(e) {
792 var value = e.value.value;
793 this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
794 this.updateCheckboxesFromPreloadEngines_();
795 $('language-options-list').updateDeletable();
799 * Handles enabledExtensionImePref change.
800 * @param {Event} e Change event.
803 handleEnabledExtensionsPrefChange_: function(e) {
804 var value = e.value.value;
805 this.enabledExtensionImes_ = value.split(',');
806 this.updateCheckboxesFromEnabledExtensions_();
810 * Handles offer-to-translate checkbox's click event.
811 * @param {Event} e Click event.
814 handleOfferToTranslateCheckboxClick_: function(e) {
815 var checkbox = e.target;
816 var checked = checkbox.checked;
818 var languageOptionsList = $('language-options-list');
819 var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
822 this.removeBlockedLanguage_(selectedLanguageCode);
824 this.addBlockedLanguage_(selectedLanguageCode);
828 * Handles input method checkbox's click event.
829 * @param {Event} e Click event.
832 handleCheckboxClick_: function(e) {
833 var checkbox = assertInstanceof(e.target, Element);
835 // Third party IMEs require additional confirmation prior to enabling due
837 if (/^_ext_ime_/.test(checkbox.inputMethodId) && checkbox.checked) {
838 var confirmationCallback = this.handleCheckboxUpdate_.bind(this,
840 var cancellationCallback = function() {
841 checkbox.checked = false;
843 ThirdPartyImeConfirmOverlay.showConfirmationDialog({
844 extension: checkbox.imeProvider,
845 confirm: confirmationCallback,
846 cancel: cancellationCallback
849 this.handleCheckboxUpdate_(checkbox);
852 chrome.send('coreOptionsUserMetricsAction',
853 ['Options_Languages_InputMethodCheckbox' +
854 (checkbox.checked ? '_Enable' : '_Disable')]);
858 * Updates active IMEs based on change in state of a checkbox for an input
860 * @param {!Element} checkbox Updated checkbox element.
863 handleCheckboxUpdate_: function(checkbox) {
864 if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
865 this.updateEnabledExtensionsFromCheckboxes_();
866 this.saveEnabledExtensionPref_();
869 if (this.preloadEngines_.length == 1 && !checkbox.checked) {
870 // Don't allow disabling the last input method.
871 this.showNotification_(
872 loadTimeData.getString('pleaseAddAnotherInputMethod'),
873 loadTimeData.getString('okButton'));
874 checkbox.checked = true;
877 if (checkbox.checked) {
878 chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
880 chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
882 this.updatePreloadEnginesFromCheckboxes_();
883 this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
884 this.savePreloadEnginesPref_();
888 * Handles clicks on the "OK" button of the "Add language" dialog.
889 * @param {Event} e Click event.
892 handleAddLanguageOkButtonClick_: function(e) {
893 var languagesSelect = $('add-language-overlay-language-list');
894 var selectedIndex = languagesSelect.selectedIndex;
895 if (selectedIndex >= 0) {
896 var selection = languagesSelect.options[selectedIndex];
897 var langCode = String(selection.value);
898 $('language-options-list').addLanguage(langCode);
899 this.addBlockedLanguage_(langCode);
900 PageManager.closeOverlay();
905 * Checks if languageCode is deletable or not.
906 * @param {string} languageCode the languageCode to check for deletability.
908 languageIsDeletable: function(languageCode) {
909 // Don't allow removing the language if it's a UI language.
910 if (languageCode == this.prospectiveUiLanguageCode_)
912 return (!cr.isChromeOS ||
913 this.canDeleteLanguage_(languageCode));
917 * Handles browse.enable_spellchecking change.
918 * @param {Event} e Change event.
921 updateEnableSpellCheck_: function(e) {
922 var value = !$('enable-spell-check').checked;
923 $('language-options-spell-check-language-button').disabled = value;
925 $('edit-dictionary-button').hidden = value;
929 * Handles translateBlockedLanguagesPref change.
930 * @param {Event} e Change event.
933 handleTranslateBlockedLanguagesPrefChange_: function(e) {
934 this.translateBlockedLanguages_ = e.value.value;
935 this.updateOfferToTranslateCheckbox_(
936 $('language-options-list').getSelectedLanguageCode());
940 * Handles spellCheckDictionaryPref change.
941 * @param {Event} e Change event.
944 handleSpellCheckDictionaryPrefChange_: function(e) {
945 var languageCode = e.value.value;
946 this.spellCheckDictionary_ = languageCode;
948 this.updateSpellCheckLanguageButton_(
949 $('language-options-list').getSelectedLanguageCode());
954 * Handles translate.enabled change.
955 * @param {Event} e Change event.
958 handleEnableTranslatePrefChange_: function(e) {
959 var enabled = e.value.value;
960 this.enableTranslate_ = enabled;
961 this.updateOfferToTranslateCheckbox_(
962 $('language-options-list').getSelectedLanguageCode());
966 * Handles spellCheckLanguageButton click.
967 * @param {Event} e Click event.
970 handleSpellCheckLanguageButtonClick_: function(e) {
971 var languageCode = e.target.languageCode;
972 // Save the preference.
973 Preferences.setStringPref(SPELL_CHECK_DICTIONARY_PREF,
975 chrome.send('spellCheckLanguageChange', [languageCode]);
976 chrome.send('coreOptionsUserMetricsAction',
977 ['Options_Languages_SpellCheck']);
981 * Checks whether it's possible to remove the language specified by
982 * languageCode and returns true if possible. This function returns false
983 * if the removal causes the number of preload engines to be zero.
985 * @param {string} languageCode Language code (ex. "fr").
986 * @return {boolean} Returns true on success.
989 canDeleteLanguage_: function(languageCode) {
990 // First create the set of engines to be removed from input methods
991 // associated with the language code.
992 var enginesToBeRemovedSet = {};
993 var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
995 // If this language doesn't have any input methods, it can be deleted.
999 for (var i = 0; i < inputMethodIds.length; i++) {
1000 enginesToBeRemovedSet[inputMethodIds[i]] = true;
1003 // Then eliminate engines that are also used for other active languages.
1004 // For instance, if "xkb:us::eng" is used for both English and Filipino.
1005 var languageCodes = $('language-options-list').getLanguageCodes();
1006 for (var i = 0; i < languageCodes.length; i++) {
1007 // Skip the target language code.
1008 if (languageCodes[i] == languageCode) {
1011 // Check if input methods used in this language are included in
1012 // enginesToBeRemovedSet. If so, eliminate these from the set, so
1013 // we don't remove this time.
1014 var inputMethodIdsForAnotherLanguage =
1015 this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
1016 if (!inputMethodIdsForAnotherLanguage)
1019 for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
1020 var inputMethodId = inputMethodIdsForAnotherLanguage[j];
1021 if (inputMethodId in enginesToBeRemovedSet) {
1022 delete enginesToBeRemovedSet[inputMethodId];
1027 // Update the preload engine list with the to-be-removed set.
1028 var newPreloadEngines = [];
1029 for (var i = 0; i < this.preloadEngines_.length; i++) {
1030 if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
1031 newPreloadEngines.push(this.preloadEngines_[i]);
1034 // Don't allow this operation if it causes the number of preload
1035 // engines to be zero.
1036 return (newPreloadEngines.length > 0);
1040 * Saves the enabled extension preference.
1043 saveEnabledExtensionPref_: function() {
1044 Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
1045 this.enabledExtensionImes_.join(','), true);
1049 * Updates the checkboxes in the input method list from the enabled
1050 * extensions preference.
1053 updateCheckboxesFromEnabledExtensions_: function() {
1054 // Convert the list into a dictonary for simpler lookup.
1055 var dictionary = {};
1056 for (var i = 0; i < this.enabledExtensionImes_.length; i++)
1057 dictionary[this.enabledExtensionImes_[i]] = true;
1059 var inputMethodList = $('language-options-input-method-list');
1060 var checkboxes = inputMethodList.querySelectorAll('input');
1061 for (var i = 0; i < checkboxes.length; i++) {
1062 if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1063 checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1065 var configureButtons = inputMethodList.querySelectorAll('button');
1066 for (var i = 0; i < configureButtons.length; i++) {
1067 if (configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1068 configureButtons[i].hidden =
1069 !(configureButtons[i].inputMethodId in dictionary);
1075 * Updates the enabled extensions preference from the checkboxes in the
1076 * input method list.
1079 updateEnabledExtensionsFromCheckboxes_: function() {
1080 this.enabledExtensionImes_ = [];
1081 var inputMethodList = $('language-options-input-method-list');
1082 var checkboxes = inputMethodList.querySelectorAll('input');
1083 for (var i = 0; i < checkboxes.length; i++) {
1084 if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1085 if (checkboxes[i].checked)
1086 this.enabledExtensionImes_.push(checkboxes[i].inputMethodId);
1092 * Saves the preload engines preference.
1095 savePreloadEnginesPref_: function() {
1096 Preferences.setStringPref(PRELOAD_ENGINES_PREF,
1097 this.preloadEngines_.join(','), true);
1101 * Updates the checkboxes in the input method list from the preload
1102 * engines preference.
1105 updateCheckboxesFromPreloadEngines_: function() {
1106 // Convert the list into a dictonary for simpler lookup.
1107 var dictionary = {};
1108 for (var i = 0; i < this.preloadEngines_.length; i++) {
1109 dictionary[this.preloadEngines_[i]] = true;
1112 var inputMethodList = $('language-options-input-method-list');
1113 var checkboxes = inputMethodList.querySelectorAll('input');
1114 for (var i = 0; i < checkboxes.length; i++) {
1115 if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1116 checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1118 var configureButtons = inputMethodList.querySelectorAll('button');
1119 for (var i = 0; i < configureButtons.length; i++) {
1120 if (!configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1121 configureButtons[i].hidden =
1122 !(configureButtons[i].inputMethodId in dictionary);
1128 * Updates the preload engines preference from the checkboxes in the
1129 * input method list.
1132 updatePreloadEnginesFromCheckboxes_: function() {
1133 this.preloadEngines_ = [];
1134 var inputMethodList = $('language-options-input-method-list');
1135 var checkboxes = inputMethodList.querySelectorAll('input');
1136 for (var i = 0; i < checkboxes.length; i++) {
1137 if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1138 if (checkboxes[i].checked)
1139 this.preloadEngines_.push(checkboxes[i].inputMethodId);
1142 var languageOptionsList = $('language-options-list');
1143 languageOptionsList.updateDeletable();
1147 * Filters bad preload engines in case bad preload engines are
1148 * stored in the preference. Removes duplicates as well.
1149 * @param {Array} preloadEngines List of preload engines.
1152 filterBadPreloadEngines_: function(preloadEngines) {
1153 // Convert the list into a dictonary for simpler lookup.
1154 var dictionary = {};
1155 var list = loadTimeData.getValue('inputMethodList');
1156 for (var i = 0; i < list.length; i++) {
1157 dictionary[list[i].id] = true;
1160 var enabledPreloadEngines = [];
1162 for (var i = 0; i < preloadEngines.length; i++) {
1163 // Check if the preload engine is present in the
1164 // dictionary, and not duplicate. Otherwise, skip it.
1165 // Component Extension IME should be handled same as preloadEngines and
1166 // "_comp_" is the special prefix of its ID.
1167 if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) ||
1168 /^_comp_/.test(preloadEngines[i])) {
1169 enabledPreloadEngines.push(preloadEngines[i]);
1170 seen[preloadEngines[i]] = true;
1173 return enabledPreloadEngines;
1176 // TODO(kochi): This is an adapted copy from new_tab.js.
1177 // If this will go as final UI, refactor this to share the component with
1178 // new new tab page.
1182 notificationTimeout_: null,
1185 * Shows notification.
1186 * @param {string} text
1187 * @param {string} actionText
1188 * @param {number=} opt_delay
1191 showNotification_: function(text, actionText, opt_delay) {
1192 var notificationElement = $('notification');
1193 var actionLink = notificationElement.querySelector('.link-color');
1194 var delay = opt_delay || 10000;
1197 window.clearTimeout(this.notificationTimeout_);
1198 notificationElement.classList.add('show');
1199 document.body.classList.add('notification-shown');
1203 window.clearTimeout(this.notificationTimeout_);
1204 notificationElement.classList.remove('show');
1205 document.body.classList.remove('notification-shown');
1206 // Prevent tabbing to the hidden link.
1207 actionLink.tabIndex = -1;
1208 // Setting tabIndex to -1 only prevents future tabbing to it. If,
1209 // however, the user switches window or a tab and then moves back to
1210 // this tab the element may gain focus. We therefore make sure that we
1211 // blur the element so that the element focus is not restored when
1212 // coming back to this window.
1216 function delayedHide() {
1217 this.notificationTimeout_ = window.setTimeout(hide, delay);
1220 notificationElement.firstElementChild.textContent = text;
1221 actionLink.textContent = actionText;
1223 actionLink.onclick = hide;
1224 actionLink.onkeydown = function(e) {
1225 if (e.keyIdentifier == 'Enter') {
1229 notificationElement.onmouseover = show;
1230 notificationElement.onmouseout = delayedHide;
1231 actionLink.onfocus = show;
1232 actionLink.onblur = delayedHide;
1233 // Enable tabbing to the link now that it is shown.
1234 actionLink.tabIndex = 0;
1241 * Chrome callback for when the UI language preference is saved.
1242 * @param {string} languageCode The newly selected language to use.
1245 uiLanguageSaved_: function(languageCode) {
1246 this.prospectiveUiLanguageCode_ = languageCode;
1248 // If the user is no longer on the same language code, ignore.
1249 if ($('language-options-list').getSelectedLanguageCode() != languageCode)
1252 // Special case for when a user changes to a different language, and
1253 // changes back to the same language without having restarted Chrome or
1254 // logged in/out of ChromeOS.
1255 if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
1256 this.updateUiLanguageButton_(languageCode);
1260 // Otherwise, show a notification telling the user that their changes will
1261 // only take effect after restart.
1262 showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
1263 $('language-options-ui-notification-bar')],
1268 * A handler for when dictionary for |languageCode| begins downloading.
1269 * @param {string} languageCode The language of the dictionary that just
1270 * began downloading.
1273 onDictionaryDownloadBegin_: function(languageCode) {
1274 this.spellcheckDictionaryDownloadStatus_[languageCode] =
1275 DOWNLOAD_STATUS.IN_PROGRESS;
1278 $('language-options-list').getSelectedLanguageCode()) {
1279 this.updateSpellCheckLanguageButton_(languageCode);
1284 * A handler for when dictionary for |languageCode| successfully downloaded.
1285 * @param {string} languageCode The language of the dictionary that
1286 * succeeded downloading.
1289 onDictionaryDownloadSuccess_: function(languageCode) {
1290 delete this.spellcheckDictionaryDownloadStatus_[languageCode];
1291 this.spellcheckDictionaryDownloadFailures_ = 0;
1294 $('language-options-list').getSelectedLanguageCode()) {
1295 this.updateSpellCheckLanguageButton_(languageCode);
1300 * A handler for when dictionary for |languageCode| fails to download.
1301 * @param {string} languageCode The language of the dictionary that failed
1305 onDictionaryDownloadFailure_: function(languageCode) {
1306 this.spellcheckDictionaryDownloadStatus_[languageCode] =
1307 DOWNLOAD_STATUS.FAILED;
1308 this.spellcheckDictionaryDownloadFailures_++;
1311 $('language-options-list').getSelectedLanguageCode()) {
1312 this.updateSpellCheckLanguageButton_(languageCode);
1317 * Converts the language code for Translation. There are some differences
1318 * between the language set for Translation and that for Accept-Language.
1319 * @param {string} languageCode The language code like 'fr'.
1320 * @return {string} The converted language code.
1323 convertLangCodeForTranslation_: function(languageCode) {
1324 var tokens = languageCode.split('-');
1325 var main = tokens[0];
1327 // See also: components/translate/core/browser/common/translate_util.cc
1338 if (main in synonyms) {
1339 return synonyms[main];
1340 } else if (main == 'zh') {
1341 // In Translation, general Chinese is not used, and the sub code is
1342 // necessary as a language code for Translate server.
1343 return languageCode;
1351 * Shows the node at |index| in |nodes|, hides all others.
1352 * @param {Array.<HTMLElement>} nodes The nodes to be shown or hidden.
1353 * @param {number} index The index of |nodes| to show.
1355 function showMutuallyExclusiveNodes(nodes, index) {
1356 assert(index >= 0 && index < nodes.length);
1357 for (var i = 0; i < nodes.length; ++i) {
1358 assert(nodes[i] instanceof HTMLElement); // TODO(dbeam): Ignore null?
1359 nodes[i].hidden = i != index;
1363 LanguageOptions.uiLanguageSaved = function(languageCode) {
1364 LanguageOptions.getInstance().uiLanguageSaved_(languageCode);
1367 LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
1368 LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
1371 LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
1372 LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
1375 LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
1376 LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
1381 LanguageOptions: LanguageOptions