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.
76 function LanguageOptions(model) {
77 Page.call(this, 'languages',
78 loadTimeData.getString('languagePageTabTitle'), 'languagePage');
81 cr.addSingletonGetter(LanguageOptions);
83 // Inherit LanguageOptions from Page.
84 LanguageOptions.prototype = {
85 __proto__: Page.prototype,
87 /* For recording the prospective language (the next locale after relaunch).
91 prospectiveUiLanguageCode_: null,
94 * Map from language code to spell check dictionary download status for that
99 spellcheckDictionaryDownloadStatus_: [],
102 * Number of times a spell check dictionary download failed.
106 spellcheckDictionaryDownloadFailures_: 0,
109 * The list of preload engines, like ['mozc', 'pinyin'].
116 * The list of extension IMEs that are enabled out of the language menu.
120 enabledExtensionImes_: [],
123 * The list of the languages which is not translated.
127 translateBlockedLanguages_: [],
130 * The list of the languages supported by Translate server
134 translateSupportedLanguages_: [],
137 * The preference is a string that describes the spell check dictionary
138 * language, like "en-US".
142 spellCheckDictionary_: '',
145 * The map of language code to input method IDs, like:
146 * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
150 languageCodeToInputMethodIdsMap_: {},
153 * The value that indicates if Translate feature is enabled or not.
157 enableTranslate_: false,
160 initializePage: function() {
161 Page.prototype.initializePage.call(this);
163 var languageOptionsList = $('language-options-list');
164 LanguageList.decorate(languageOptionsList);
166 languageOptionsList.addEventListener('change',
167 this.handleLanguageOptionsListChange_.bind(this));
168 languageOptionsList.addEventListener('save',
169 this.handleLanguageOptionsListSave_.bind(this));
171 this.prospectiveUiLanguageCode_ =
172 loadTimeData.getString('prospectiveUiLanguageCode');
173 this.addEventListener('visibleChange',
174 this.handleVisibleChange_.bind(this));
177 this.initializeInputMethodList_();
178 this.initializeLanguageCodeToInputMethodIdsMap_();
181 var checkbox = $('offer-to-translate-in-this-language');
182 checkbox.addEventListener('click',
183 this.handleOfferToTranslateCheckboxClick_.bind(this));
185 Preferences.getInstance().addEventListener(
186 TRANSLATE_BLOCKED_LANGUAGES_PREF,
187 this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
188 Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARY_PREF,
189 this.handleSpellCheckDictionaryPrefChange_.bind(this));
190 Preferences.getInstance().addEventListener(ENABLE_TRANSLATE,
191 this.handleEnableTranslatePrefChange_.bind(this));
192 this.translateSupportedLanguages_ =
193 loadTimeData.getValue('translateSupportedLanguages');
195 // Set up add button.
196 var onclick = function(e) {
197 // Add the language without showing the overlay if it's specified in
198 // the URL hash (ex. lang_add=ja). Used for automated testing.
199 var match = document.location.hash.match(/\blang_add=([\w-]+)/);
201 var addLanguageCode = match[1];
202 $('language-options-list').addLanguage(addLanguageCode);
203 this.addBlockedLanguage_(addLanguageCode);
205 PageManager.showPageByName('addLanguage');
208 $('language-options-add-button').onclick = onclick.bind(this);
211 // Set up the button for editing custom spelling dictionary.
212 $('edit-dictionary-button').onclick = function(e) {
213 PageManager.showPageByName('editDictionary');
215 $('dictionary-download-retry-button').onclick = function(e) {
216 chrome.send('retryDictionaryDownload');
220 // Listen to add language dialog ok button.
221 $('add-language-overlay-ok-button').addEventListener(
222 'click', this.handleAddLanguageOkButtonClick_.bind(this));
224 if (!cr.isChromeOS) {
225 // Show experimental features if enabled.
226 if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
227 $('auto-spell-correction-option').hidden = false;
229 // Handle spell check enable/disable.
231 Preferences.getInstance().addEventListener(
232 ENABLE_SPELL_CHECK_PREF,
233 this.updateEnableSpellCheck_.bind(this));
237 // Handle clicks on "Use this language for spell checking" button.
239 var spellCheckLanguageButton = getRequiredElement(
240 'language-options-spell-check-language-button');
241 spellCheckLanguageButton.addEventListener(
243 this.handleSpellCheckLanguageButtonClick_.bind(this));
247 $('language-options-ui-restart-button').onclick = function() {
248 chrome.send('uiLanguageRestart');
252 $('language-confirm').onclick =
253 PageManager.closeOverlay.bind(PageManager);
255 // Public session users cannot change the locale.
256 if (cr.isChromeOS && UIAccountTweaks.loggedInAsPublicAccount())
257 $('language-options-ui-language-section').hidden = true;
258 PageManager.closeOverlay.bind(PageManager);
262 * Initializes the input method list.
264 initializeInputMethodList_: function() {
265 var inputMethodList = $('language-options-input-method-list');
266 var inputMethodPrototype = $('language-options-input-method-template');
268 // Add all input methods, but make all of them invisible here. We'll
269 // change the visibility in handleLanguageOptionsListChange_() based
270 // on the selected language. Note that we only have less than 100
271 // input methods, so creating DOM nodes at once here should be ok.
272 this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
273 this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
274 this.appendComponentExtensionIme_(
275 loadTimeData.getValue('componentExtensionImeList'));
277 // Listen to pref change once the input method list is initialized.
278 Preferences.getInstance().addEventListener(
279 PRELOAD_ENGINES_PREF,
280 this.handlePreloadEnginesPrefChange_.bind(this));
281 Preferences.getInstance().addEventListener(
282 ENABLED_EXTENSION_IME_PREF,
283 this.handleEnabledExtensionsPrefChange_.bind(this));
287 * Appends input method lists based on component extension ime list.
288 * @param {!Array} componentExtensionImeList A list of input method
292 appendComponentExtensionIme_: function(componentExtensionImeList) {
293 this.appendInputMethodElement_(componentExtensionImeList);
295 for (var i = 0; i < componentExtensionImeList.length; i++) {
296 var inputMethod = componentExtensionImeList[i];
297 for (var languageCode in inputMethod.languageCodeSet) {
298 if (languageCode in this.languageCodeToInputMethodIdsMap_) {
299 this.languageCodeToInputMethodIdsMap_[languageCode].push(
302 this.languageCodeToInputMethodIdsMap_[languageCode] =
310 * Appends input methods into input method list.
311 * @param {!Array} inputMethods A list of input method descriptors.
314 appendInputMethodElement_: function(inputMethods) {
315 var inputMethodList = $('language-options-input-method-list');
316 var inputMethodTemplate = $('language-options-input-method-template');
318 for (var i = 0; i < inputMethods.length; i++) {
319 var inputMethod = inputMethods[i];
320 var element = inputMethodTemplate.cloneNode(true);
322 element.languageCodeSet = inputMethod.languageCodeSet;
324 var input = element.querySelector('input');
325 input.inputMethodId = inputMethod.id;
326 input.imeProvider = inputMethod.extensionName;
327 var span = element.querySelector('span');
328 span.textContent = inputMethod.displayName;
330 if (inputMethod.optionsPage) {
331 var button = document.createElement('button');
332 button.textContent = loadTimeData.getString('configure');
333 button.inputMethodId = inputMethod.id;
334 button.onclick = function(inputMethodId, e) {
335 chrome.send('inputMethodOptionsOpen', [inputMethodId]);
337 button.onclick = button.onclick.bind(this, inputMethod.id);
338 element.appendChild(button);
341 // Listen to user clicks.
342 input.addEventListener('click',
343 this.handleCheckboxClick_.bind(this));
344 inputMethodList.appendChild(element);
349 * Adds a language to the preference 'translate_blocked_languages'. If
350 * |langCode| is already added, nothing happens. |langCode| is converted
351 * to a Translate language synonym before added.
352 * @param {string} langCode A language code like 'en'
355 addBlockedLanguage_: function(langCode) {
356 langCode = this.convertLangCodeForTranslation_(langCode);
357 if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
358 this.translateBlockedLanguages_.push(langCode);
359 Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
360 this.translateBlockedLanguages_, true);
365 * Removes a language from the preference 'translate_blocked_languages'.
366 * If |langCode| doesn't exist in the preference, nothing happens.
367 * |langCode| is converted to a Translate language synonym before removed.
368 * @param {string} langCode A language code like 'en'
371 removeBlockedLanguage_: function(langCode) {
372 langCode = this.convertLangCodeForTranslation_(langCode);
373 if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
374 this.translateBlockedLanguages_ =
375 this.translateBlockedLanguages_.filter(
376 function(langCodeNotTranslated) {
377 return langCodeNotTranslated != langCode;
379 Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
380 this.translateBlockedLanguages_, true);
385 * Handles Page's visible property change event.
386 * @param {Event} e Property change event.
389 handleVisibleChange_: function(e) {
391 $('language-options-list').redraw();
392 chrome.send('languageOptionsOpen');
397 * Handles languageOptionsList's change event.
398 * @param {Event} e Change event.
401 handleLanguageOptionsListChange_: function(e) {
402 var languageOptionsList = $('language-options-list');
403 var languageCode = languageOptionsList.getSelectedLanguageCode();
405 // If there's no selection, just return.
409 // Select the language if it's specified in the URL hash (ex. lang=ja).
410 // Used for automated testing.
411 var match = document.location.hash.match(/\blang=([\w-]+)/);
413 var specifiedLanguageCode = match[1];
414 if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
415 languageCode = specifiedLanguageCode;
419 this.updateOfferToTranslateCheckbox_(languageCode);
421 if (cr.isWindows || cr.isChromeOS)
422 this.updateUiLanguageButton_(languageCode);
424 this.updateSelectedLanguageName_(languageCode);
427 this.updateSpellCheckLanguageButton_(languageCode);
430 this.updateInputMethodList_(languageCode);
432 this.updateLanguageListInAddLanguageOverlay_();
436 * Handles languageOptionsList's save event.
437 * @param {Event} e Save event.
440 handleLanguageOptionsListSave_: function(e) {
442 // Sort the preload engines per the saved languages before save.
443 this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
444 this.savePreloadEnginesPref_();
449 * Sorts preloadEngines_ by languageOptionsList's order.
450 * @param {Array} preloadEngines List of preload engines.
451 * @return {Array} Returns sorted preloadEngines.
454 sortPreloadEngines_: function(preloadEngines) {
455 // For instance, suppose we have two languages and associated input
461 // The preloadEngines preference should look like "hangul,pinyin".
462 // If the user reverse the order, the preference should be reorderd
463 // to "pinyin,hangul".
464 var languageOptionsList = $('language-options-list');
465 var languageCodes = languageOptionsList.getLanguageCodes();
467 // Convert the list into a dictonary for simpler lookup.
468 var preloadEngineSet = {};
469 for (var i = 0; i < preloadEngines.length; i++) {
470 preloadEngineSet[preloadEngines[i]] = true;
473 // Create the new preload engine list per the language codes.
474 var newPreloadEngines = [];
475 for (var i = 0; i < languageCodes.length; i++) {
476 var languageCode = languageCodes[i];
477 var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
482 // Check if we have active input methods associated with the language.
483 for (var j = 0; j < inputMethodIds.length; j++) {
484 var inputMethodId = inputMethodIds[j];
485 if (inputMethodId in preloadEngineSet) {
486 // If we have, add it to the new engine list.
487 newPreloadEngines.push(inputMethodId);
488 // And delete it from the set. This is necessary as one input
489 // method can be associated with more than one language thus
490 // we should avoid having duplicates in the new list.
491 delete preloadEngineSet[inputMethodId];
496 return newPreloadEngines;
500 * Initializes the map of language code to input method IDs.
503 initializeLanguageCodeToInputMethodIdsMap_: function() {
504 var inputMethodList = loadTimeData.getValue('inputMethodList');
505 for (var i = 0; i < inputMethodList.length; i++) {
506 var inputMethod = inputMethodList[i];
507 for (var languageCode in inputMethod.languageCodeSet) {
508 if (languageCode in this.languageCodeToInputMethodIdsMap_) {
509 this.languageCodeToInputMethodIdsMap_[languageCode].push(
512 this.languageCodeToInputMethodIdsMap_[languageCode] =
520 * Updates the currently selected language name.
521 * @param {string} languageCode Language code (ex. "fr").
524 updateSelectedLanguageName_: function(languageCode) {
525 var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
527 var languageDisplayName = languageInfo.displayName;
528 var languageNativeDisplayName = languageInfo.nativeDisplayName;
529 var textDirection = languageInfo.textDirection;
531 // If the native name is different, add it.
532 if (languageDisplayName != languageNativeDisplayName) {
533 languageDisplayName += ' - ' + languageNativeDisplayName;
536 // Update the currently selected language name.
537 var languageName = $('language-options-language-name');
538 languageName.textContent = languageDisplayName;
539 languageName.dir = textDirection;
543 * Updates the UI language button.
544 * @param {string} languageCode Language code (ex. "fr").
547 updateUiLanguageButton_: function(languageCode) {
548 var uiLanguageButton = $('language-options-ui-language-button');
549 var uiLanguageMessage = $('language-options-ui-language-message');
550 var uiLanguageNotification = $('language-options-ui-notification-bar');
552 // Remove the event listener and add it back if useful.
553 uiLanguageButton.onclick = null;
555 // Unhide the language button every time, as it could've been previously
556 // hidden by a language change.
557 uiLanguageButton.hidden = false;
559 // Hide the controlled setting indicator.
560 var uiLanguageIndicator = document.querySelector(
561 '.language-options-contents .controlled-setting-indicator');
562 uiLanguageIndicator.removeAttribute('controlled-by');
564 if (languageCode == this.prospectiveUiLanguageCode_) {
565 uiLanguageMessage.textContent =
566 loadTimeData.getString('isDisplayedInThisLanguage');
567 showMutuallyExclusiveNodes(
568 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
569 } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
570 if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
571 // In the guest mode for ChromeOS, changing UI language does not make
572 // sense because it does not take effect after browser restart.
573 uiLanguageButton.hidden = true;
574 uiLanguageMessage.hidden = true;
576 uiLanguageButton.textContent =
577 loadTimeData.getString('displayInThisLanguage');
579 if (loadTimeData.valueExists('secondaryUser') &&
580 loadTimeData.getBoolean('secondaryUser')) {
581 uiLanguageButton.disabled = true;
582 uiLanguageIndicator.setAttribute('controlled-by', 'shared');
584 uiLanguageButton.onclick = function(e) {
585 chrome.send('uiLanguageChange', [languageCode]);
588 showMutuallyExclusiveNodes(
589 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
592 uiLanguageMessage.textContent =
593 loadTimeData.getString('cannotBeDisplayedInThisLanguage');
594 showMutuallyExclusiveNodes(
595 [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
600 * Updates the spell check language button.
601 * @param {string} languageCode Language code (ex. "fr").
604 updateSpellCheckLanguageButton_: function(languageCode) {
605 var spellCheckLanguageSection = $('language-options-spellcheck');
606 var spellCheckLanguageButton =
607 $('language-options-spell-check-language-button');
608 var spellCheckLanguageMessage =
609 $('language-options-spell-check-language-message');
610 var dictionaryDownloadInProgress =
611 $('language-options-dictionary-downloading-message');
612 var dictionaryDownloadFailed =
613 $('language-options-dictionary-download-failed-message');
614 var dictionaryDownloadFailHelp =
615 $('language-options-dictionary-download-fail-help-message');
616 spellCheckLanguageSection.hidden = false;
617 spellCheckLanguageMessage.hidden = true;
618 spellCheckLanguageButton.hidden = true;
619 dictionaryDownloadInProgress.hidden = true;
620 dictionaryDownloadFailed.hidden = true;
621 dictionaryDownloadFailHelp.hidden = true;
623 if (languageCode == this.spellCheckDictionary_) {
624 if (!(languageCode in this.spellcheckDictionaryDownloadStatus_)) {
625 spellCheckLanguageMessage.textContent =
626 loadTimeData.getString('isUsedForSpellChecking');
627 showMutuallyExclusiveNodes(
628 [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
629 } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
630 DOWNLOAD_STATUS.IN_PROGRESS) {
631 dictionaryDownloadInProgress.hidden = false;
632 } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
633 DOWNLOAD_STATUS.FAILED) {
634 spellCheckLanguageSection.hidden = true;
635 dictionaryDownloadFailed.hidden = false;
636 if (this.spellcheckDictionaryDownloadFailures_ > 1)
637 dictionaryDownloadFailHelp.hidden = false;
639 } else if (languageCode in
640 loadTimeData.getValue('spellCheckLanguageCodeSet')) {
641 spellCheckLanguageButton.textContent =
642 loadTimeData.getString('useThisForSpellChecking');
643 showMutuallyExclusiveNodes(
644 [spellCheckLanguageButton, spellCheckLanguageMessage], 0);
645 spellCheckLanguageButton.languageCode = languageCode;
646 } else if (!languageCode) {
647 spellCheckLanguageButton.hidden = true;
648 spellCheckLanguageMessage.hidden = true;
650 spellCheckLanguageMessage.textContent =
651 loadTimeData.getString('cannotBeUsedForSpellChecking');
652 showMutuallyExclusiveNodes(
653 [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
658 * Updates the checkbox for stopping translation.
659 * @param {string} languageCode Language code (ex. "fr").
662 updateOfferToTranslateCheckbox_: function(languageCode) {
663 var div = $('language-options-offer-to-translate');
665 // Translation server supports Chinese (Transitional) and Chinese
666 // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
667 // show this preference when general Chinese is selected.
668 if (languageCode != 'zh') {
675 var offerToTranslate = div.querySelector('div');
676 var cannotTranslate = $('cannot-translate-in-this-language');
677 var nodes = [offerToTranslate, cannotTranslate];
679 var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
680 if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
681 showMutuallyExclusiveNodes(nodes, 0);
683 showMutuallyExclusiveNodes(nodes, 1);
687 var checkbox = $('offer-to-translate-in-this-language');
689 if (!this.enableTranslate_) {
690 checkbox.disabled = true;
691 checkbox.checked = false;
695 // If the language corresponds to the default target language (in most
696 // cases, the user's locale language), "Offer to translate" checkbox
697 // should be always unchecked.
698 var defaultTargetLanguage =
699 loadTimeData.getString('defaultTargetLanguage');
700 if (convertedLangCode == defaultTargetLanguage) {
701 checkbox.disabled = true;
702 checkbox.checked = false;
706 checkbox.disabled = false;
708 var blockedLanguages = this.translateBlockedLanguages_;
709 var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
710 checkbox.checked = checked;
714 * Updates the input method list.
715 * @param {string} languageCode Language code (ex. "fr").
718 updateInputMethodList_: function(languageCode) {
719 // Give one of the checkboxes or buttons focus, if it's specified in the
720 // URL hash (ex. focus=mozc). Used for automated testing.
721 var focusInputMethodId = -1;
722 var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
724 focusInputMethodId = match[1];
726 // Change the visibility of the input method list. Input methods that
727 // matches |languageCode| will become visible.
728 var inputMethodList = $('language-options-input-method-list');
729 var methods = inputMethodList.querySelectorAll('.input-method');
730 for (var i = 0; i < methods.length; i++) {
731 var method = methods[i];
732 if (languageCode in method.languageCodeSet) {
733 method.hidden = false;
734 var input = method.querySelector('input');
735 // Give it focus if the ID matches.
736 if (input.inputMethodId == focusInputMethodId) {
740 method.hidden = true;
744 $('language-options-input-method-none').hidden =
745 (languageCode in this.languageCodeToInputMethodIdsMap_);
747 if (focusInputMethodId == 'add') {
748 $('language-options-add-button').focus();
753 * Updates the language list in the add language overlay.
754 * @param {string} languageCode Language code (ex. "fr").
757 updateLanguageListInAddLanguageOverlay_: function(languageCode) {
758 // Change the visibility of the language list in the add language
759 // overlay. Languages that are already active will become invisible,
760 // so that users don't add the same language twice.
761 var languageOptionsList = $('language-options-list');
762 var languageCodes = languageOptionsList.getLanguageCodes();
763 var languageCodeSet = {};
764 for (var i = 0; i < languageCodes.length; i++) {
765 languageCodeSet[languageCodes[i]] = true;
768 var addLanguageList = $('add-language-overlay-language-list');
769 var options = addLanguageList.querySelectorAll('option');
770 assert(options.length > 0);
771 var selectedFirstItem = false;
772 for (var i = 0; i < options.length; i++) {
773 var option = options[i];
774 option.hidden = option.value in languageCodeSet;
775 if (!option.hidden && !selectedFirstItem) {
776 // Select first visible item, otherwise previously selected hidden
777 // item will be selected by default at the next time.
778 option.selected = true;
779 selectedFirstItem = true;
785 * Handles preloadEnginesPref change.
786 * @param {Event} e Change event.
789 handlePreloadEnginesPrefChange_: function(e) {
790 var value = e.value.value;
791 this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
792 this.updateCheckboxesFromPreloadEngines_();
793 $('language-options-list').updateDeletable();
797 * Handles enabledExtensionImePref change.
798 * @param {Event} e Change event.
801 handleEnabledExtensionsPrefChange_: function(e) {
802 var value = e.value.value;
803 this.enabledExtensionImes_ = value.split(',');
804 this.updateCheckboxesFromEnabledExtensions_();
808 * Handles offer-to-translate checkbox's click event.
809 * @param {Event} e Click event.
812 handleOfferToTranslateCheckboxClick_: function(e) {
813 var checkbox = e.target;
814 var checked = checkbox.checked;
816 var languageOptionsList = $('language-options-list');
817 var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
820 this.removeBlockedLanguage_(selectedLanguageCode);
822 this.addBlockedLanguage_(selectedLanguageCode);
826 * Handles input method checkbox's click event.
827 * @param {Event} e Click event.
830 handleCheckboxClick_: function(e) {
831 var checkbox = e.target;
833 // Third party IMEs require additional confirmation prior to enabling due
835 if (/^_ext_ime_/.test(checkbox.inputMethodId) && checkbox.checked) {
836 var confirmationCallback = this.handleCheckboxUpdate_.bind(this,
838 var cancellationCallback = function() {
839 checkbox.checked = false;
841 ThirdPartyImeConfirmOverlay.showConfirmationDialog({
842 extension: checkbox.imeProvider,
843 confirm: confirmationCallback,
844 cancel: cancellationCallback
847 this.handleCheckboxUpdate_(checkbox);
852 * Updates active IMEs based on change in state of a checkbox for an input
854 * @param {!Element} checkbox Updated checkbox element.
857 handleCheckboxUpdate_: function(checkbox) {
858 if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
859 this.updateEnabledExtensionsFromCheckboxes_();
860 this.saveEnabledExtensionPref_();
863 if (this.preloadEngines_.length == 1 && !checkbox.checked) {
864 // Don't allow disabling the last input method.
865 this.showNotification_(
866 loadTimeData.getString('pleaseAddAnotherInputMethod'),
867 loadTimeData.getString('okButton'));
868 checkbox.checked = true;
871 if (checkbox.checked) {
872 chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
874 chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
876 this.updatePreloadEnginesFromCheckboxes_();
877 this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
878 this.savePreloadEnginesPref_();
882 * Handles clicks on the "OK" button of the "Add language" dialog.
883 * @param {Event} e Click event.
886 handleAddLanguageOkButtonClick_: function(e) {
887 var languagesSelect = $('add-language-overlay-language-list');
888 var selectedIndex = languagesSelect.selectedIndex;
889 if (selectedIndex >= 0) {
890 var selection = languagesSelect.options[selectedIndex];
891 var langCode = String(selection.value);
892 $('language-options-list').addLanguage(langCode);
893 this.addBlockedLanguage_(langCode);
894 PageManager.closeOverlay();
899 * Checks if languageCode is deletable or not.
900 * @param {string} languageCode the languageCode to check for deletability.
902 languageIsDeletable: function(languageCode) {
903 // Don't allow removing the language if it's a UI language.
904 if (languageCode == this.prospectiveUiLanguageCode_)
906 return (!cr.isChromeOS ||
907 this.canDeleteLanguage_(languageCode));
911 * Handles browse.enable_spellchecking change.
912 * @param {Event} e Change event.
915 updateEnableSpellCheck_: function() {
916 var value = !$('enable-spell-check').checked;
917 $('language-options-spell-check-language-button').disabled = value;
919 $('edit-dictionary-button').hidden = value;
923 * Handles translateBlockedLanguagesPref change.
924 * @param {Event} e Change event.
927 handleTranslateBlockedLanguagesPrefChange_: function(e) {
928 this.translateBlockedLanguages_ = e.value.value;
929 this.updateOfferToTranslateCheckbox_(
930 $('language-options-list').getSelectedLanguageCode());
934 * Handles spellCheckDictionaryPref change.
935 * @param {Event} e Change event.
938 handleSpellCheckDictionaryPrefChange_: function(e) {
939 var languageCode = e.value.value;
940 this.spellCheckDictionary_ = languageCode;
942 this.updateSpellCheckLanguageButton_(
943 $('language-options-list').getSelectedLanguageCode());
948 * Handles translate.enabled change.
949 * @param {Event} e Change event.
952 handleEnableTranslatePrefChange_: function(e) {
953 var enabled = e.value.value;
954 this.enableTranslate_ = enabled;
955 this.updateOfferToTranslateCheckbox_(
956 $('language-options-list').getSelectedLanguageCode());
960 * Handles spellCheckLanguageButton click.
961 * @param {Event} e Click event.
964 handleSpellCheckLanguageButtonClick_: function(e) {
965 var languageCode = e.target.languageCode;
966 // Save the preference.
967 Preferences.setStringPref(SPELL_CHECK_DICTIONARY_PREF,
969 chrome.send('spellCheckLanguageChange', [languageCode]);
973 * Checks whether it's possible to remove the language specified by
974 * languageCode and returns true if possible. This function returns false
975 * if the removal causes the number of preload engines to be zero.
977 * @param {string} languageCode Language code (ex. "fr").
978 * @return {boolean} Returns true on success.
981 canDeleteLanguage_: function(languageCode) {
982 // First create the set of engines to be removed from input methods
983 // associated with the language code.
984 var enginesToBeRemovedSet = {};
985 var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
987 // If this language doesn't have any input methods, it can be deleted.
991 for (var i = 0; i < inputMethodIds.length; i++) {
992 enginesToBeRemovedSet[inputMethodIds[i]] = true;
995 // Then eliminate engines that are also used for other active languages.
996 // For instance, if "xkb:us::eng" is used for both English and Filipino.
997 var languageCodes = $('language-options-list').getLanguageCodes();
998 for (var i = 0; i < languageCodes.length; i++) {
999 // Skip the target language code.
1000 if (languageCodes[i] == languageCode) {
1003 // Check if input methods used in this language are included in
1004 // enginesToBeRemovedSet. If so, eliminate these from the set, so
1005 // we don't remove this time.
1006 var inputMethodIdsForAnotherLanguage =
1007 this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
1008 if (!inputMethodIdsForAnotherLanguage)
1011 for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
1012 var inputMethodId = inputMethodIdsForAnotherLanguage[j];
1013 if (inputMethodId in enginesToBeRemovedSet) {
1014 delete enginesToBeRemovedSet[inputMethodId];
1019 // Update the preload engine list with the to-be-removed set.
1020 var newPreloadEngines = [];
1021 for (var i = 0; i < this.preloadEngines_.length; i++) {
1022 if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
1023 newPreloadEngines.push(this.preloadEngines_[i]);
1026 // Don't allow this operation if it causes the number of preload
1027 // engines to be zero.
1028 return (newPreloadEngines.length > 0);
1032 * Saves the enabled extension preference.
1035 saveEnabledExtensionPref_: function() {
1036 Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
1037 this.enabledExtensionImes_.join(','), true);
1041 * Updates the checkboxes in the input method list from the enabled
1042 * extensions preference.
1045 updateCheckboxesFromEnabledExtensions_: function() {
1046 // Convert the list into a dictonary for simpler lookup.
1047 var dictionary = {};
1048 for (var i = 0; i < this.enabledExtensionImes_.length; i++)
1049 dictionary[this.enabledExtensionImes_[i]] = true;
1051 var inputMethodList = $('language-options-input-method-list');
1052 var checkboxes = inputMethodList.querySelectorAll('input');
1053 for (var i = 0; i < checkboxes.length; i++) {
1054 if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1055 checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1057 var configureButtons = inputMethodList.querySelectorAll('button');
1058 for (var i = 0; i < configureButtons.length; i++) {
1059 if (configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1060 configureButtons[i].hidden =
1061 !(configureButtons[i].inputMethodId in dictionary);
1067 * Updates the enabled extensions preference from the checkboxes in the
1068 * input method list.
1071 updateEnabledExtensionsFromCheckboxes_: function() {
1072 this.enabledExtensionImes_ = [];
1073 var inputMethodList = $('language-options-input-method-list');
1074 var checkboxes = inputMethodList.querySelectorAll('input');
1075 for (var i = 0; i < checkboxes.length; i++) {
1076 if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1077 if (checkboxes[i].checked)
1078 this.enabledExtensionImes_.push(checkboxes[i].inputMethodId);
1084 * Saves the preload engines preference.
1087 savePreloadEnginesPref_: function() {
1088 Preferences.setStringPref(PRELOAD_ENGINES_PREF,
1089 this.preloadEngines_.join(','), true);
1093 * Updates the checkboxes in the input method list from the preload
1094 * engines preference.
1097 updateCheckboxesFromPreloadEngines_: function() {
1098 // Convert the list into a dictonary for simpler lookup.
1099 var dictionary = {};
1100 for (var i = 0; i < this.preloadEngines_.length; i++) {
1101 dictionary[this.preloadEngines_[i]] = true;
1104 var inputMethodList = $('language-options-input-method-list');
1105 var checkboxes = inputMethodList.querySelectorAll('input');
1106 for (var i = 0; i < checkboxes.length; i++) {
1107 if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
1108 checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
1110 var configureButtons = inputMethodList.querySelectorAll('button');
1111 for (var i = 0; i < configureButtons.length; i++) {
1112 if (!configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
1113 configureButtons[i].hidden =
1114 !(configureButtons[i].inputMethodId in dictionary);
1120 * Updates the preload engines preference from the checkboxes in the
1121 * input method list.
1124 updatePreloadEnginesFromCheckboxes_: function() {
1125 this.preloadEngines_ = [];
1126 var inputMethodList = $('language-options-input-method-list');
1127 var checkboxes = inputMethodList.querySelectorAll('input');
1128 for (var i = 0; i < checkboxes.length; i++) {
1129 if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
1130 if (checkboxes[i].checked)
1131 this.preloadEngines_.push(checkboxes[i].inputMethodId);
1134 var languageOptionsList = $('language-options-list');
1135 languageOptionsList.updateDeletable();
1139 * Filters bad preload engines in case bad preload engines are
1140 * stored in the preference. Removes duplicates as well.
1141 * @param {Array} preloadEngines List of preload engines.
1144 filterBadPreloadEngines_: function(preloadEngines) {
1145 // Convert the list into a dictonary for simpler lookup.
1146 var dictionary = {};
1147 var list = loadTimeData.getValue('inputMethodList');
1148 for (var i = 0; i < list.length; i++) {
1149 dictionary[list[i].id] = true;
1152 var enabledPreloadEngines = [];
1154 for (var i = 0; i < preloadEngines.length; i++) {
1155 // Check if the preload engine is present in the
1156 // dictionary, and not duplicate. Otherwise, skip it.
1157 // Component Extension IME should be handled same as preloadEngines and
1158 // "_comp_" is the special prefix of its ID.
1159 if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) ||
1160 /^_comp_/.test(preloadEngines[i])) {
1161 enabledPreloadEngines.push(preloadEngines[i]);
1162 seen[preloadEngines[i]] = true;
1165 return enabledPreloadEngines;
1168 // TODO(kochi): This is an adapted copy from new_tab.js.
1169 // If this will go as final UI, refactor this to share the component with
1170 // new new tab page.
1172 * Shows notification
1175 notificationTimeout_: null,
1176 showNotification_: function(text, actionText, opt_delay) {
1177 var notificationElement = $('notification');
1178 var actionLink = notificationElement.querySelector('.link-color');
1179 var delay = opt_delay || 10000;
1182 window.clearTimeout(this.notificationTimeout_);
1183 notificationElement.classList.add('show');
1184 document.body.classList.add('notification-shown');
1188 window.clearTimeout(this.notificationTimeout_);
1189 notificationElement.classList.remove('show');
1190 document.body.classList.remove('notification-shown');
1191 // Prevent tabbing to the hidden link.
1192 actionLink.tabIndex = -1;
1193 // Setting tabIndex to -1 only prevents future tabbing to it. If,
1194 // however, the user switches window or a tab and then moves back to
1195 // this tab the element may gain focus. We therefore make sure that we
1196 // blur the element so that the element focus is not restored when
1197 // coming back to this window.
1201 function delayedHide() {
1202 this.notificationTimeout_ = window.setTimeout(hide, delay);
1205 notificationElement.firstElementChild.textContent = text;
1206 actionLink.textContent = actionText;
1208 actionLink.onclick = hide;
1209 actionLink.onkeydown = function(e) {
1210 if (e.keyIdentifier == 'Enter') {
1214 notificationElement.onmouseover = show;
1215 notificationElement.onmouseout = delayedHide;
1216 actionLink.onfocus = show;
1217 actionLink.onblur = delayedHide;
1218 // Enable tabbing to the link now that it is shown.
1219 actionLink.tabIndex = 0;
1226 * Chrome callback for when the UI language preference is saved.
1227 * @param {string} languageCode The newly selected language to use.
1230 uiLanguageSaved_: function(languageCode) {
1231 this.prospectiveUiLanguageCode_ = languageCode;
1233 // If the user is no longer on the same language code, ignore.
1234 if ($('language-options-list').getSelectedLanguageCode() != languageCode)
1237 // Special case for when a user changes to a different language, and
1238 // changes back to the same language without having restarted Chrome or
1239 // logged in/out of ChromeOS.
1240 if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
1241 this.updateUiLanguageButton_(languageCode);
1245 // Otherwise, show a notification telling the user that their changes will
1246 // only take effect after restart.
1247 showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
1248 $('language-options-ui-notification-bar')],
1253 * A handler for when dictionary for |languageCode| begins downloading.
1254 * @param {string} languageCode The language of the dictionary that just
1255 * began downloading.
1258 onDictionaryDownloadBegin_: function(languageCode) {
1259 this.spellcheckDictionaryDownloadStatus_[languageCode] =
1260 DOWNLOAD_STATUS.IN_PROGRESS;
1263 $('language-options-list').getSelectedLanguageCode()) {
1264 this.updateSpellCheckLanguageButton_(languageCode);
1269 * A handler for when dictionary for |languageCode| successfully downloaded.
1270 * @param {string} languageCode The language of the dictionary that
1271 * succeeded downloading.
1274 onDictionaryDownloadSuccess_: function(languageCode) {
1275 delete this.spellcheckDictionaryDownloadStatus_[languageCode];
1276 this.spellcheckDictionaryDownloadFailures_ = 0;
1279 $('language-options-list').getSelectedLanguageCode()) {
1280 this.updateSpellCheckLanguageButton_(languageCode);
1285 * A handler for when dictionary for |languageCode| fails to download.
1286 * @param {string} languageCode The language of the dictionary that failed
1290 onDictionaryDownloadFailure_: function(languageCode) {
1291 this.spellcheckDictionaryDownloadStatus_[languageCode] =
1292 DOWNLOAD_STATUS.FAILED;
1293 this.spellcheckDictionaryDownloadFailures_++;
1296 $('language-options-list').getSelectedLanguageCode()) {
1297 this.updateSpellCheckLanguageButton_(languageCode);
1302 * Converts the language code for Translation. There are some differences
1303 * between the language set for Translation and that for Accept-Language.
1304 * @param {string} languageCode The language code like 'fr'.
1305 * @return {string} The converted language code.
1308 convertLangCodeForTranslation_: function(languageCode) {
1309 var tokens = languageCode.split('-');
1310 var main = tokens[0];
1312 // See also: chrome/renderer/translate/translate_helper.cc.
1320 if (main in synonyms) {
1321 return synonyms[main];
1322 } else if (main == 'zh') {
1323 // In Translation, general Chinese is not used, and the sub code is
1324 // necessary as a language code for Translate server.
1325 return languageCode;
1333 * Shows the node at |index| in |nodes|, hides all others.
1334 * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
1335 * @param {number} index The index of |nodes| to show.
1337 function showMutuallyExclusiveNodes(nodes, index) {
1338 assert(index >= 0 && index < nodes.length);
1339 for (var i = 0; i < nodes.length; ++i) {
1340 assert(nodes[i] instanceof HTMLElement); // TODO(dbeam): Ignore null?
1341 nodes[i].hidden = i != index;
1345 LanguageOptions.uiLanguageSaved = function(languageCode) {
1346 LanguageOptions.getInstance().uiLanguageSaved_(languageCode);
1349 LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
1350 LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
1353 LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
1354 LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
1357 LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
1358 LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
1363 LanguageOptions: LanguageOptions