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