1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 // TODO(kochi): Generalize the notification as a component and put it
6 // in js/cr/ui/notification.js .
8 cr
.define('options', function() {
9 /** @const */ var Page
= cr
.ui
.pageManager
.Page
;
10 /** @const */ var PageManager
= cr
.ui
.pageManager
.PageManager
;
11 /** @const */ var LanguageList
= options
.LanguageList
;
12 /** @const */ var ThirdPartyImeConfirmOverlay
=
13 options
.ThirdPartyImeConfirmOverlay
;
16 * Spell check dictionary download status.
19 /** @const*/ var DOWNLOAD_STATUS
= {
25 * The preference is a boolean that enables/disables spell checking.
29 var ENABLE_SPELL_CHECK_PREF
= 'browser.enable_spellchecking';
32 * The preference is a CSV string that describes preload engines
33 * (i.e. active input methods).
37 var PRELOAD_ENGINES_PREF
= 'settings.language.preload_engines';
40 * The preference that lists the extension IMEs that are enabled in the
45 var ENABLED_EXTENSION_IME_PREF
= 'settings.language.enabled_extension_imes';
48 * The preference that lists the languages which are not translated.
52 var TRANSLATE_BLOCKED_LANGUAGES_PREF
= 'translate_blocked_languages';
55 * The preference key that is a list of strings that describes the spellcheck
56 * dictionary language, like ["en-US", "fr"].
60 var SPELL_CHECK_DICTIONARIES_PREF
= 'spellcheck.dictionaries';
63 * The preference that indicates if the Translate feature is enabled.
67 var ENABLE_TRANSLATE
= 'translate.enabled';
69 /////////////////////////////////////////////////////////////////////////////
70 // LanguageOptions class:
73 * Encapsulated handling of ChromeOS language options page.
75 * @extends {cr.ui.pageManager.Page}
77 function LanguageOptions(model
) {
78 Page
.call(this, 'languages',
79 loadTimeData
.getString('languagePageTabTitle'), 'languagePage');
82 cr
.addSingletonGetter(LanguageOptions
);
84 // Inherit LanguageOptions from Page.
85 LanguageOptions
.prototype = {
86 __proto__
: Page
.prototype,
89 * For recording the prospective language (the next locale after relaunch).
93 prospectiveUiLanguageCode_
: null,
96 * Map from language code to spell check dictionary download status for that
101 spellcheckDictionaryDownloadStatus_
: [],
104 * Number of times a spell check dictionary download failed.
108 spellcheckDictionaryDownloadFailures_
: 0,
111 * The list of preload engines, like ['mozc', 'pinyin'].
118 * The list of extension IMEs that are enabled out of the language menu.
122 enabledExtensionImes_
: [],
125 * The list of the languages which is not translated.
129 translateBlockedLanguages_
: [],
132 * The list of the languages supported by Translate server
136 translateSupportedLanguages_
: [],
139 * The dictionary of currently selected spellcheck dictionary languages,
140 * like {"en-US": true, "sl-SI": true}.
144 spellCheckLanguages_
: {},
147 * The map of language code to input method IDs, like:
148 * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
152 languageCodeToInputMethodIdsMap_
: {},
155 * The value that indicates if Translate feature is enabled or not.
159 enableTranslate_
: false,
162 * Returns true if the enable-multilingual-spellchecker flag is set.
166 isMultilingualSpellcheckerEnabled_: function() {
167 return loadTimeData
.getBoolean('enableMultilingualSpellChecker');
171 initializePage: function() {
172 Page
.prototype.initializePage
.call(this);
174 var languageOptionsList
= $('language-options-list');
175 LanguageList
.decorate(languageOptionsList
);
177 languageOptionsList
.addEventListener('change',
178 this.handleLanguageOptionsListChange_
.bind(this));
179 languageOptionsList
.addEventListener('save',
180 this.handleLanguageOptionsListSave_
.bind(this));
182 this.prospectiveUiLanguageCode_
=
183 loadTimeData
.getString('prospectiveUiLanguageCode');
184 this.addEventListener('visibleChange',
185 this.handleVisibleChange_
.bind(this));
188 this.initializeInputMethodList_();
189 this.initializeLanguageCodeToInputMethodIdsMap_();
192 var checkbox
= $('offer-to-translate-in-this-language');
193 checkbox
.addEventListener('click',
194 this.handleOfferToTranslateCheckboxClick_
.bind(this));
196 Preferences
.getInstance().addEventListener(
197 TRANSLATE_BLOCKED_LANGUAGES_PREF
,
198 this.handleTranslateBlockedLanguagesPrefChange_
.bind(this));
199 Preferences
.getInstance().addEventListener(SPELL_CHECK_DICTIONARIES_PREF
,
200 this.handleSpellCheckDictionariesPrefChange_
.bind(this));
201 Preferences
.getInstance().addEventListener(ENABLE_TRANSLATE
,
202 this.handleEnableTranslatePrefChange_
.bind(this));
203 this.translateSupportedLanguages_
=
204 loadTimeData
.getValue('translateSupportedLanguages');
206 // Set up add button.
207 var onclick = function(e
) {
208 // Add the language without showing the overlay if it's specified in
209 // the URL hash (ex. lang_add=ja). Used for automated testing.
210 var match
= document
.location
.hash
.match(/\blang_add=([\w-]+)/);
212 var addLanguageCode
= match
[1];
213 $('language-options-list').addLanguage(addLanguageCode
);
214 this.addBlockedLanguage_(addLanguageCode
);
216 PageManager
.showPageByName('addLanguage');
217 chrome
.send('coreOptionsUserMetricsAction',
218 ['Options_Languages_Add']);
221 $('language-options-add-button').onclick
= onclick
.bind(this);
224 // Set up the button for editing custom spelling dictionary.
225 $('edit-custom-dictionary-button').onclick = function(e
) {
226 PageManager
.showPageByName('editDictionary');
228 $('dictionary-download-retry-button').onclick = function(e
) {
229 chrome
.send('retryDictionaryDownload');
233 // Listen to add language dialog ok button.
234 $('add-language-overlay-ok-button').addEventListener(
235 'click', this.handleAddLanguageOkButtonClick_
.bind(this));
237 if (!cr
.isChromeOS
) {
238 // Show experimental features if enabled.
239 if (loadTimeData
.getBoolean('enableSpellingAutoCorrect'))
240 $('auto-spell-correction-option').hidden
= false;
243 if (!(cr
.isMac
|| cr
.isChromeOS
)) {
244 // Handle spell check enable/disable.
245 if (!this.isMultilingualSpellcheckerEnabled_()) {
246 Preferences
.getInstance().addEventListener(
247 ENABLE_SPELL_CHECK_PREF
, this.updateEnableSpellCheck_
.bind(this));
249 $('enable-spellcheck-container').hidden
=
250 this.isMultilingualSpellcheckerEnabled_();
253 // Handle clicks on "Use this language for spell checking" button.
255 if (this.isMultilingualSpellcheckerEnabled_()) {
256 $('spellcheck-language-checkbox').addEventListener(
258 this.handleSpellCheckLanguageCheckboxClick_
.bind(this));
260 $('spellcheck-language-button').addEventListener(
262 this.handleSpellCheckLanguageButtonClick_
.bind(this));
267 $('language-options-ui-restart-button').onclick = function() {
268 chrome
.send('uiLanguageRestart');
272 $('language-confirm').onclick
=
273 PageManager
.closeOverlay
.bind(PageManager
);
275 // Public session users cannot change the locale.
276 if (cr
.isChromeOS
&& UIAccountTweaks
.loggedInAsPublicAccount())
277 $('language-options-ui-language-section').hidden
= true;
278 PageManager
.closeOverlay
.bind(PageManager
);
282 * Initializes the input method list.
284 initializeInputMethodList_: function() {
285 var inputMethodList
= $('language-options-input-method-list');
286 var inputMethodPrototype
= $('language-options-input-method-template');
288 // Add all input methods, but make all of them invisible here. We'll
289 // change the visibility in handleLanguageOptionsListChange_() based
290 // on the selected language. Note that we only have less than 100
291 // input methods, so creating DOM nodes at once here should be ok.
292 this.appendInputMethodElement_(loadTimeData
.getValue('inputMethodList'));
293 this.appendComponentExtensionIme_(
294 loadTimeData
.getValue('componentExtensionImeList'));
295 this.appendInputMethodElement_(loadTimeData
.getValue('extensionImeList'));
297 // Listen to pref change once the input method list is initialized.
298 Preferences
.getInstance().addEventListener(
299 PRELOAD_ENGINES_PREF
,
300 this.handlePreloadEnginesPrefChange_
.bind(this));
301 Preferences
.getInstance().addEventListener(
302 ENABLED_EXTENSION_IME_PREF
,
303 this.handleEnabledExtensionsPrefChange_
.bind(this));
307 * Appends input method lists based on component extension ime list.
308 * @param {!Array} componentExtensionImeList A list of input method
312 appendComponentExtensionIme_: function(componentExtensionImeList
) {
313 this.appendInputMethodElement_(componentExtensionImeList
);
315 for (var i
= 0; i
< componentExtensionImeList
.length
; i
++) {
316 var inputMethod
= componentExtensionImeList
[i
];
317 for (var languageCode
in inputMethod
.languageCodeSet
) {
318 if (languageCode
in this.languageCodeToInputMethodIdsMap_
) {
319 this.languageCodeToInputMethodIdsMap_
[languageCode
].push(
322 this.languageCodeToInputMethodIdsMap_
[languageCode
] =
330 * Appends input methods into input method list.
331 * @param {!Array} inputMethods A list of input method descriptors.
334 appendInputMethodElement_: function(inputMethods
) {
335 var inputMethodList
= $('language-options-input-method-list');
336 var inputMethodTemplate
= $('language-options-input-method-template');
338 for (var i
= 0; i
< inputMethods
.length
; i
++) {
339 var inputMethod
= inputMethods
[i
];
340 var element
= inputMethodTemplate
.cloneNode(true);
342 element
.languageCodeSet
= inputMethod
.languageCodeSet
;
344 var input
= element
.querySelector('input');
345 input
.inputMethodId
= inputMethod
.id
;
346 input
.imeProvider
= inputMethod
.extensionName
;
347 var span
= element
.querySelector('span');
348 span
.textContent
= inputMethod
.displayName
;
350 if (inputMethod
.optionsPage
) {
351 var button
= document
.createElement('button');
352 button
.textContent
= loadTimeData
.getString('configure');
353 button
.inputMethodId
= inputMethod
.id
;
354 button
.onclick = function(inputMethodId
, e
) {
355 chrome
.send('inputMethodOptionsOpen', [inputMethodId
]);
356 }.bind(this, inputMethod
.id
);
357 element
.appendChild(button
);
360 // Listen to user clicks.
361 input
.addEventListener('click',
362 this.handleCheckboxClick_
.bind(this));
363 inputMethodList
.appendChild(element
);
368 * Adds a language to the preference 'translate_blocked_languages'. If
369 * |langCode| is already added, nothing happens. |langCode| is converted
370 * to a Translate language synonym before added.
371 * @param {string} langCode A language code like 'en'
374 addBlockedLanguage_: function(langCode
) {
375 langCode
= this.convertLangCodeForTranslation_(langCode
);
376 if (this.translateBlockedLanguages_
.indexOf(langCode
) == -1) {
377 this.translateBlockedLanguages_
.push(langCode
);
378 Preferences
.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF
,
379 this.translateBlockedLanguages_
, true);
384 * Removes a language from the preference 'translate_blocked_languages'.
385 * If |langCode| doesn't exist in the preference, nothing happens.
386 * |langCode| is converted to a Translate language synonym before removed.
387 * @param {string} langCode A language code like 'en'
390 removeBlockedLanguage_: function(langCode
) {
391 langCode
= this.convertLangCodeForTranslation_(langCode
);
392 if (this.translateBlockedLanguages_
.indexOf(langCode
) != -1) {
393 this.translateBlockedLanguages_
=
394 this.translateBlockedLanguages_
.filter(
395 function(langCodeNotTranslated
) {
396 return langCodeNotTranslated
!= langCode
;
398 Preferences
.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF
,
399 this.translateBlockedLanguages_
, true);
404 * Handles Page's visible property change event.
405 * @param {Event} e Property change event.
408 handleVisibleChange_: function(e
) {
410 $('language-options-list').redraw();
411 chrome
.send('languageOptionsOpen');
416 * Handles languageOptionsList's change event.
417 * @param {Event} e Change event.
420 handleLanguageOptionsListChange_: function(e
) {
421 var languageOptionsList
= $('language-options-list');
422 var languageCode
= languageOptionsList
.getSelectedLanguageCode();
424 // If there's no selection, just return.
428 // Select the language if it's specified in the URL hash (ex. lang=ja).
429 // Used for automated testing.
430 var match
= document
.location
.hash
.match(/\blang=([\w-]+)/);
432 var specifiedLanguageCode
= match
[1];
433 if (languageOptionsList
.selectLanguageByCode(specifiedLanguageCode
)) {
434 languageCode
= specifiedLanguageCode
;
438 this.updateOfferToTranslateCheckbox_(languageCode
);
440 if (cr
.isWindows
|| cr
.isChromeOS
)
441 this.updateUiLanguageButton_(languageCode
);
443 this.updateSelectedLanguageName_(languageCode
);
446 this.updateSpellCheckLanguageControls_(languageCode
);
449 this.updateInputMethodList_(languageCode
);
451 this.updateLanguageListInAddLanguageOverlay_();
455 * Handles languageOptionsList's save event.
456 * @param {Event} e Save event.
459 handleLanguageOptionsListSave_: function(e
) {
461 // Sort the preload engines per the saved languages before save.
462 this.preloadEngines_
= this.sortPreloadEngines_(this.preloadEngines_
);
463 this.savePreloadEnginesPref_();
468 * Sorts preloadEngines_ by languageOptionsList's order.
469 * @param {Array} preloadEngines List of preload engines.
470 * @return {Array} Returns sorted preloadEngines.
473 sortPreloadEngines_: function(preloadEngines
) {
474 // For instance, suppose we have two languages and associated input
480 // The preloadEngines preference should look like "hangul,pinyin".
481 // If the user reverse the order, the preference should be reorderd
482 // to "pinyin,hangul".
483 var languageOptionsList
= $('language-options-list');
484 var languageCodes
= languageOptionsList
.getLanguageCodes();
486 // Convert the list into a dictonary for simpler lookup.
487 var preloadEngineSet
= {};
488 for (var i
= 0; i
< preloadEngines
.length
; i
++) {
489 preloadEngineSet
[preloadEngines
[i
]] = true;
492 // Create the new preload engine list per the language codes.
493 var newPreloadEngines
= [];
494 for (var i
= 0; i
< languageCodes
.length
; i
++) {
495 var languageCode
= languageCodes
[i
];
496 var inputMethodIds
= this.languageCodeToInputMethodIdsMap_
[
501 // Check if we have active input methods associated with the language.
502 for (var j
= 0; j
< inputMethodIds
.length
; j
++) {
503 var inputMethodId
= inputMethodIds
[j
];
504 if (inputMethodId
in preloadEngineSet
) {
505 // If we have, add it to the new engine list.
506 newPreloadEngines
.push(inputMethodId
);
507 // And delete it from the set. This is necessary as one input
508 // method can be associated with more than one language thus
509 // we should avoid having duplicates in the new list.
510 delete preloadEngineSet
[inputMethodId
];
515 return newPreloadEngines
;
519 * Initializes the map of language code to input method IDs.
522 initializeLanguageCodeToInputMethodIdsMap_: function() {
523 var inputMethodList
= loadTimeData
.getValue('inputMethodList');
524 for (var i
= 0; i
< inputMethodList
.length
; i
++) {
525 var inputMethod
= inputMethodList
[i
];
526 for (var languageCode
in inputMethod
.languageCodeSet
) {
527 if (languageCode
in this.languageCodeToInputMethodIdsMap_
) {
528 this.languageCodeToInputMethodIdsMap_
[languageCode
].push(
531 this.languageCodeToInputMethodIdsMap_
[languageCode
] =
539 * Updates the currently selected language name.
540 * @param {string} languageCode Language code (ex. "fr").
543 updateSelectedLanguageName_: function(languageCode
) {
544 var languageInfo
= LanguageList
.getLanguageInfoFromLanguageCode(
546 var languageDisplayName
= languageInfo
.displayName
;
547 var languageNativeDisplayName
= languageInfo
.nativeDisplayName
;
548 var textDirection
= languageInfo
.textDirection
;
550 // If the native name is different, add it.
551 if (languageDisplayName
!= languageNativeDisplayName
) {
552 languageDisplayName
+= ' - ' + languageNativeDisplayName
;
555 // Update the currently selected language name.
556 var languageName
= $('language-options-language-name');
557 languageName
.textContent
= languageDisplayName
;
558 languageName
.dir
= textDirection
;
562 * Updates the UI language button.
563 * @param {string} languageCode Language code (ex. "fr").
566 updateUiLanguageButton_: function(languageCode
) {
567 var uiLanguageButton
= $('language-options-ui-language-button');
568 var uiLanguageMessage
= $('language-options-ui-language-message');
569 var uiLanguageNotification
= $('language-options-ui-notification-bar');
571 // Remove the event listener and add it back if useful.
572 uiLanguageButton
.onclick
= null;
574 // Unhide the language button every time, as it could've been previously
575 // hidden by a language change.
576 uiLanguageButton
.hidden
= false;
578 // Hide the controlled setting indicator.
579 var uiLanguageIndicator
= document
.querySelector(
580 '.language-options-contents .controlled-setting-indicator');
581 uiLanguageIndicator
.removeAttribute('controlled-by');
583 if (languageCode
== this.prospectiveUiLanguageCode_
) {
584 uiLanguageMessage
.textContent
=
585 loadTimeData
.getString('isDisplayedInThisLanguage');
586 showMutuallyExclusiveNodes(
587 [uiLanguageButton
, uiLanguageMessage
, uiLanguageNotification
], 1);
588 } else if (languageCode
in loadTimeData
.getValue('uiLanguageCodeSet')) {
589 if (cr
.isChromeOS
&& UIAccountTweaks
.loggedInAsGuest()) {
590 // In the guest mode for ChromeOS, changing UI language does not make
591 // sense because it does not take effect after browser restart.
592 uiLanguageButton
.hidden
= true;
593 uiLanguageMessage
.hidden
= true;
595 uiLanguageButton
.textContent
=
596 loadTimeData
.getString('displayInThisLanguage');
598 if (loadTimeData
.valueExists('secondaryUser') &&
599 loadTimeData
.getBoolean('secondaryUser')) {
600 uiLanguageButton
.disabled
= true;
601 uiLanguageIndicator
.setAttribute('controlled-by', 'shared');
603 uiLanguageButton
.onclick = function(e
) {
604 chrome
.send('uiLanguageChange', [languageCode
]);
607 showMutuallyExclusiveNodes(
608 [uiLanguageButton
, uiLanguageMessage
, uiLanguageNotification
], 0);
611 uiLanguageMessage
.textContent
=
612 loadTimeData
.getString('cannotBeDisplayedInThisLanguage');
613 showMutuallyExclusiveNodes(
614 [uiLanguageButton
, uiLanguageMessage
, uiLanguageNotification
], 1);
619 * Updates the spell check language button/checkbox, dictionary download
620 * dialog, and the "Enable spell checking" checkbox.
621 * @param {string} languageCode Language code (ex. "fr").
624 updateSpellCheckLanguageControls_: function(languageCode
) {
625 assert(languageCode
);
626 var spellCheckLanguageSection
= $('language-options-spellcheck');
627 var spellCheckLanguageButton
= $('spellcheck-language-button');
628 var spellCheckLanguageCheckboxContainer
=
629 $('spellcheck-language-checkbox-container');
630 var spellCheckLanguageCheckbox
= $('spellcheck-language-checkbox');
631 var spellCheckLanguageMessage
= $('spellcheck-language-message');
632 var dictionaryDownloadInProgress
=
633 $('language-options-dictionary-downloading-message');
634 var dictionaryDownloadFailed
=
635 $('language-options-dictionary-download-failed-message');
636 var dictionaryDownloadFailHelp
=
637 $('language-options-dictionary-download-fail-help-message');
639 spellCheckLanguageSection
.hidden
= false;
640 spellCheckLanguageMessage
.hidden
= true;
641 spellCheckLanguageButton
.hidden
= true;
642 spellCheckLanguageCheckboxContainer
.hidden
= true;
643 dictionaryDownloadInProgress
.hidden
= true;
644 dictionaryDownloadFailed
.hidden
= true;
645 dictionaryDownloadFailHelp
.hidden
= true;
646 spellCheckLanguageCheckbox
.checked
= false;
648 var canBeUsedForSpellchecking
=
649 languageCode
in loadTimeData
.getValue('spellCheckLanguageCodeSet');
651 if (!canBeUsedForSpellchecking
) {
652 spellCheckLanguageMessage
.textContent
=
653 loadTimeData
.getString('cannotBeUsedForSpellChecking');
654 spellCheckLanguageMessage
.hidden
= false;
658 var isUsedForSpellchecking
= languageCode
in this.spellCheckLanguages_
;
659 var isLanguageDownloaded
=
660 !(languageCode
in this.spellcheckDictionaryDownloadStatus_
);
662 if (this.isMultilingualSpellcheckerEnabled_()) {
663 spellCheckLanguageCheckbox
.languageCode
= languageCode
;
664 spellCheckLanguageCheckbox
.checked
= isUsedForSpellchecking
;
665 spellCheckLanguageCheckboxContainer
.hidden
= false;
666 } else if (isUsedForSpellchecking
) {
667 if (isLanguageDownloaded
) {
668 spellCheckLanguageMessage
.textContent
=
669 loadTimeData
.getString('isUsedForSpellChecking');
670 spellCheckLanguageMessage
.hidden
= false;
673 spellCheckLanguageButton
.textContent
=
674 loadTimeData
.getString('useThisForSpellChecking');
675 spellCheckLanguageButton
.hidden
= false;
676 spellCheckLanguageButton
.languageCode
= languageCode
;
679 switch (this.spellcheckDictionaryDownloadStatus_
[languageCode
]) {
680 case DOWNLOAD_STATUS
.IN_PROGRESS
:
681 dictionaryDownloadInProgress
.hidden
= false;
683 case DOWNLOAD_STATUS
.FAILED
:
684 showMutuallyExclusiveNodes(
685 [spellCheckLanguageSection
, dictionaryDownloadFailed
], 1);
686 if (this.spellcheckDictionaryDownloadFailures_
> 1)
687 dictionaryDownloadFailHelp
.hidden
= false;
691 var areNoLanguagesSelected
=
692 Object
.keys(this.spellCheckLanguages_
).length
== 0;
693 var usesSystemSpellchecker
= !$('enable-spellcheck-container');
694 var isSpellcheckingEnabled
= usesSystemSpellchecker
||
695 this.isMultilingualSpellcheckerEnabled_() ||
696 $('enable-spellcheck').checked
;
697 $('edit-custom-dictionary-button').hidden
=
698 areNoLanguagesSelected
|| !isSpellcheckingEnabled
;
702 * Updates the checkbox for stopping translation.
703 * @param {string} languageCode Language code (ex. "fr").
706 updateOfferToTranslateCheckbox_: function(languageCode
) {
707 var div
= $('language-options-offer-to-translate');
709 // Translation server supports Chinese (Transitional) and Chinese
710 // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
711 // show this preference when general Chinese is selected.
712 if (languageCode
!= 'zh') {
719 var offerToTranslate
= div
.querySelector('div');
720 var cannotTranslate
= $('cannot-translate-in-this-language');
721 var nodes
= [offerToTranslate
, cannotTranslate
];
723 var convertedLangCode
= this.convertLangCodeForTranslation_(languageCode
);
724 if (this.translateSupportedLanguages_
.indexOf(convertedLangCode
) != -1) {
725 showMutuallyExclusiveNodes(nodes
, 0);
727 showMutuallyExclusiveNodes(nodes
, 1);
731 var checkbox
= $('offer-to-translate-in-this-language');
733 if (!this.enableTranslate_
) {
734 checkbox
.disabled
= true;
735 checkbox
.checked
= false;
739 // If the language corresponds to the default target language (in most
740 // cases, the user's locale language), "Offer to translate" checkbox
741 // should be always unchecked.
742 var defaultTargetLanguage
=
743 loadTimeData
.getString('defaultTargetLanguage');
744 if (convertedLangCode
== defaultTargetLanguage
) {
745 checkbox
.disabled
= true;
746 checkbox
.checked
= false;
750 checkbox
.disabled
= false;
752 var blockedLanguages
= this.translateBlockedLanguages_
;
753 var checked
= blockedLanguages
.indexOf(convertedLangCode
) == -1;
754 checkbox
.checked
= checked
;
758 * Updates the input method list.
759 * @param {string} languageCode Language code (ex. "fr").
762 updateInputMethodList_: function(languageCode
) {
763 // Give one of the checkboxes or buttons focus, if it's specified in the
764 // URL hash (ex. focus=mozc). Used for automated testing.
765 var focusInputMethodId
= -1;
766 var match
= document
.location
.hash
.match(/\bfocus=([\w:-]+)\b/);
768 focusInputMethodId
= match
[1];
770 // Change the visibility of the input method list. Input methods that
771 // matches |languageCode| will become visible.
772 var inputMethodList
= $('language-options-input-method-list');
773 var methods
= inputMethodList
.querySelectorAll('.input-method');
774 for (var i
= 0; i
< methods
.length
; i
++) {
775 var method
= methods
[i
];
776 if (languageCode
in method
.languageCodeSet
) {
777 method
.hidden
= false;
778 var input
= method
.querySelector('input');
779 // Give it focus if the ID matches.
780 if (input
.inputMethodId
== focusInputMethodId
) {
784 method
.hidden
= true;
788 $('language-options-input-method-none').hidden
=
789 (languageCode
in this.languageCodeToInputMethodIdsMap_
);
791 if (focusInputMethodId
== 'add') {
792 $('language-options-add-button').focus();
797 * Updates the language list in the add language overlay.
800 updateLanguageListInAddLanguageOverlay_: function() {
801 // Change the visibility of the language list in the add language
802 // overlay. Languages that are already active will become invisible,
803 // so that users don't add the same language twice.
804 var languageOptionsList
= $('language-options-list');
805 var languageCodes
= languageOptionsList
.getLanguageCodes();
806 var languageCodeSet
= {};
807 for (var i
= 0; i
< languageCodes
.length
; i
++) {
808 languageCodeSet
[languageCodes
[i
]] = true;
811 var addLanguageList
= $('add-language-overlay-language-list');
812 var options
= addLanguageList
.querySelectorAll('option');
813 assert(options
.length
> 0);
814 var selectedFirstItem
= false;
815 for (var i
= 0; i
< options
.length
; i
++) {
816 var option
= options
[i
];
817 option
.hidden
= option
.value
in languageCodeSet
;
818 if (!option
.hidden
&& !selectedFirstItem
) {
819 // Select first visible item, otherwise previously selected hidden
820 // item will be selected by default at the next time.
821 option
.selected
= true;
822 selectedFirstItem
= true;
828 * Handles preloadEnginesPref change.
829 * @param {Event} e Change event.
832 handlePreloadEnginesPrefChange_: function(e
) {
833 var value
= e
.value
.value
;
834 this.preloadEngines_
= this.filterBadPreloadEngines_(value
.split(','));
835 this.updateCheckboxesFromPreloadEngines_();
836 $('language-options-list').updateDeletable();
840 * Handles enabledExtensionImePref change.
841 * @param {Event} e Change event.
844 handleEnabledExtensionsPrefChange_: function(e
) {
845 var value
= e
.value
.value
;
846 this.enabledExtensionImes_
= value
.split(',');
847 this.updateCheckboxesFromEnabledExtensions_();
851 * Handles offer-to-translate checkbox's click event.
852 * @param {Event} e Click event.
855 handleOfferToTranslateCheckboxClick_: function(e
) {
856 var checkbox
= e
.target
;
857 var checked
= checkbox
.checked
;
859 var languageOptionsList
= $('language-options-list');
860 var selectedLanguageCode
= languageOptionsList
.getSelectedLanguageCode();
863 this.removeBlockedLanguage_(selectedLanguageCode
);
865 this.addBlockedLanguage_(selectedLanguageCode
);
869 * Handles input method checkbox's click event.
870 * @param {Event} e Click event.
873 handleCheckboxClick_: function(e
) {
874 var checkbox
= assertInstanceof(e
.target
, Element
);
876 // Third party IMEs require additional confirmation prior to enabling due
878 if (/^_ext_ime_/.test(checkbox
.inputMethodId
) && checkbox
.checked
) {
879 var confirmationCallback
= this.handleCheckboxUpdate_
.bind(this,
881 var cancellationCallback = function() {
882 checkbox
.checked
= false;
884 ThirdPartyImeConfirmOverlay
.showConfirmationDialog({
885 extension
: checkbox
.imeProvider
,
886 confirm
: confirmationCallback
,
887 cancel
: cancellationCallback
890 this.handleCheckboxUpdate_(checkbox
);
893 chrome
.send('coreOptionsUserMetricsAction',
894 ['Options_Languages_InputMethodCheckbox' +
895 (checkbox
.checked
? '_Enable' : '_Disable')]);
899 * Updates active IMEs based on change in state of a checkbox for an input
901 * @param {!Element} checkbox Updated checkbox element.
904 handleCheckboxUpdate_: function(checkbox
) {
905 if (checkbox
.inputMethodId
.match(/^_ext_ime_/)) {
906 this.updateEnabledExtensionsFromCheckboxes_();
907 this.saveEnabledExtensionPref_();
910 if (this.preloadEngines_
.length
== 1 && !checkbox
.checked
) {
911 // Don't allow disabling the last input method.
912 this.showNotification_(
913 loadTimeData
.getString('pleaseAddAnotherInputMethod'),
914 loadTimeData
.getString('okButton'));
915 checkbox
.checked
= true;
918 if (checkbox
.checked
) {
919 chrome
.send('inputMethodEnable', [checkbox
.inputMethodId
]);
921 chrome
.send('inputMethodDisable', [checkbox
.inputMethodId
]);
923 this.updatePreloadEnginesFromCheckboxes_();
924 this.preloadEngines_
= this.sortPreloadEngines_(this.preloadEngines_
);
925 this.savePreloadEnginesPref_();
929 * Handles clicks on the "OK" button of the "Add language" dialog.
930 * @param {Event} e Click event.
933 handleAddLanguageOkButtonClick_: function(e
) {
934 var languagesSelect
= $('add-language-overlay-language-list');
935 var selectedIndex
= languagesSelect
.selectedIndex
;
936 if (selectedIndex
>= 0) {
937 var selection
= languagesSelect
.options
[selectedIndex
];
938 var langCode
= String(selection
.value
);
939 $('language-options-list').addLanguage(langCode
);
940 this.addBlockedLanguage_(langCode
);
941 PageManager
.closeOverlay();
946 * Checks if languageCode is deletable or not.
947 * @param {string} languageCode the languageCode to check for deletability.
949 languageIsDeletable: function(languageCode
) {
950 // Don't allow removing the language if it's a UI language.
951 if (languageCode
== this.prospectiveUiLanguageCode_
)
953 return (!cr
.isChromeOS
||
954 this.canDeleteLanguage_(languageCode
));
958 * Handles browse.enable_spellchecking change.
959 * @param {Event} e Change event.
962 updateEnableSpellCheck_: function(e
) {
963 var value
= !$('enable-spellcheck').checked
;
964 var languageControl
= $(this.isMultilingualSpellcheckerEnabled_() ?
965 'spellcheck-language-checkbox' : 'spellcheck-language-button');
966 languageControl
.disabled
= value
;
968 $('edit-custom-dictionary-button').hidden
= value
;
972 * Handles translateBlockedLanguagesPref change.
973 * @param {Event} e Change event.
976 handleTranslateBlockedLanguagesPrefChange_: function(e
) {
977 this.translateBlockedLanguages_
= e
.value
.value
;
978 this.updateOfferToTranslateCheckbox_(
979 $('language-options-list').getSelectedLanguageCode());
983 * Updates spellcheck dictionary UI (checkboxes, buttons, and labels) when
984 * preferences change.
985 * @param {Event} e Preference change event where e.value.value is the list
986 * of languages currently used for spellchecking.
989 handleSpellCheckDictionariesPrefChange_: function(e
) {
993 var languages
= e
.value
.value
;
994 this.spellCheckLanguages_
= {};
995 for (var i
= 0; i
< languages
.length
; i
++) {
996 this.spellCheckLanguages_
[languages
[i
]] = true;
998 this.updateSpellCheckLanguageControls_(
999 $('language-options-list').getSelectedLanguageCode());
1003 * Handles translate.enabled change.
1004 * @param {Event} e Change event.
1007 handleEnableTranslatePrefChange_: function(e
) {
1008 var enabled
= e
.value
.value
;
1009 this.enableTranslate_
= enabled
;
1010 this.updateOfferToTranslateCheckbox_(
1011 $('language-options-list').getSelectedLanguageCode());
1015 * Handles spellCheckLanguageButton click.
1016 * @param {Event} e Click event.
1019 handleSpellCheckLanguageButtonClick_: function(e
) {
1020 var languageCode
= e
.currentTarget
.languageCode
;
1021 // Save the preference.
1022 Preferences
.setListPref(SPELL_CHECK_DICTIONARIES_PREF
,
1023 [languageCode
], true);
1025 // The spellCheckLanguageChange argument is only used for logging.
1026 chrome
.send('spellCheckLanguageChange', [languageCode
]);
1027 chrome
.send('coreOptionsUserMetricsAction',
1028 ['Options_Languages_SpellCheck']);
1032 * Updates the spellcheck.dictionaries preference with the currently
1033 * selected language codes.
1034 * @param {Event} e Click event. e.currentTarget represents the "Use this
1035 * language for spellchecking" checkbox.
1038 handleSpellCheckLanguageCheckboxClick_: function(e
) {
1039 var languageCode
= e
.currentTarget
.languageCode
;
1041 if (e
.currentTarget
.checked
)
1042 this.spellCheckLanguages_
[languageCode
] = true;
1044 delete this.spellCheckLanguages_
[languageCode
];
1046 var languageCodes
= Object
.keys(this.spellCheckLanguages_
);
1047 Preferences
.setListPref(SPELL_CHECK_DICTIONARIES_PREF
,
1048 languageCodes
, true);
1050 // The spellCheckLanguageChange argument is only used for logging.
1051 chrome
.send('spellCheckLanguageChange', [languageCodes
.join(',')]);
1052 chrome
.send('coreOptionsUserMetricsAction',
1053 ['Options_Languages_SpellCheck']);
1057 * Checks whether it's possible to remove the language specified by
1058 * languageCode and returns true if possible. This function returns false
1059 * if the removal causes the number of preload engines to be zero.
1061 * @param {string} languageCode Language code (ex. "fr").
1062 * @return {boolean} Returns true on success.
1065 canDeleteLanguage_: function(languageCode
) {
1066 // First create the set of engines to be removed from input methods
1067 // associated with the language code.
1068 var enginesToBeRemovedSet
= {};
1069 var inputMethodIds
= this.languageCodeToInputMethodIdsMap_
[languageCode
];
1071 // If this language doesn't have any input methods, it can be deleted.
1072 if (!inputMethodIds
)
1075 for (var i
= 0; i
< inputMethodIds
.length
; i
++) {
1076 enginesToBeRemovedSet
[inputMethodIds
[i
]] = true;
1079 // Then eliminate engines that are also used for other active languages.
1080 // For instance, if "xkb:us::eng" is used for both English and Filipino.
1081 var languageCodes
= $('language-options-list').getLanguageCodes();
1082 for (var i
= 0; i
< languageCodes
.length
; i
++) {
1083 // Skip the target language code.
1084 if (languageCodes
[i
] == languageCode
) {
1087 // Check if input methods used in this language are included in
1088 // enginesToBeRemovedSet. If so, eliminate these from the set, so
1089 // we don't remove this time.
1090 var inputMethodIdsForAnotherLanguage
=
1091 this.languageCodeToInputMethodIdsMap_
[languageCodes
[i
]];
1092 if (!inputMethodIdsForAnotherLanguage
)
1095 for (var j
= 0; j
< inputMethodIdsForAnotherLanguage
.length
; j
++) {
1096 var inputMethodId
= inputMethodIdsForAnotherLanguage
[j
];
1097 if (inputMethodId
in enginesToBeRemovedSet
) {
1098 delete enginesToBeRemovedSet
[inputMethodId
];
1103 // Update the preload engine list with the to-be-removed set.
1104 var newPreloadEngines
= [];
1105 for (var i
= 0; i
< this.preloadEngines_
.length
; i
++) {
1106 if (!(this.preloadEngines_
[i
] in enginesToBeRemovedSet
)) {
1107 newPreloadEngines
.push(this.preloadEngines_
[i
]);
1110 // Don't allow this operation if it causes the number of preload
1111 // engines to be zero.
1112 return (newPreloadEngines
.length
> 0);
1116 * Saves the enabled extension preference.
1119 saveEnabledExtensionPref_: function() {
1120 Preferences
.setStringPref(ENABLED_EXTENSION_IME_PREF
,
1121 this.enabledExtensionImes_
.join(','), true);
1125 * Updates the checkboxes in the input method list from the enabled
1126 * extensions preference.
1129 updateCheckboxesFromEnabledExtensions_: function() {
1130 // Convert the list into a dictonary for simpler lookup.
1131 var dictionary
= {};
1132 for (var i
= 0; i
< this.enabledExtensionImes_
.length
; i
++)
1133 dictionary
[this.enabledExtensionImes_
[i
]] = true;
1135 var inputMethodList
= $('language-options-input-method-list');
1136 var checkboxes
= inputMethodList
.querySelectorAll('input');
1137 for (var i
= 0; i
< checkboxes
.length
; i
++) {
1138 if (checkboxes
[i
].inputMethodId
.match(/^_ext_ime_/))
1139 checkboxes
[i
].checked
= (checkboxes
[i
].inputMethodId
in dictionary
);
1141 var configureButtons
= inputMethodList
.querySelectorAll('button');
1142 for (var i
= 0; i
< configureButtons
.length
; i
++) {
1143 if (configureButtons
[i
].inputMethodId
.match(/^_ext_ime_/)) {
1144 configureButtons
[i
].hidden
=
1145 !(configureButtons
[i
].inputMethodId
in dictionary
);
1151 * Updates the enabled extensions preference from the checkboxes in the
1152 * input method list.
1155 updateEnabledExtensionsFromCheckboxes_: function() {
1156 this.enabledExtensionImes_
= [];
1157 var inputMethodList
= $('language-options-input-method-list');
1158 var checkboxes
= inputMethodList
.querySelectorAll('input');
1159 for (var i
= 0; i
< checkboxes
.length
; i
++) {
1160 if (checkboxes
[i
].inputMethodId
.match(/^_ext_ime_/)) {
1161 if (checkboxes
[i
].checked
)
1162 this.enabledExtensionImes_
.push(checkboxes
[i
].inputMethodId
);
1168 * Saves the preload engines preference.
1171 savePreloadEnginesPref_: function() {
1172 Preferences
.setStringPref(PRELOAD_ENGINES_PREF
,
1173 this.preloadEngines_
.join(','), true);
1177 * Updates the checkboxes in the input method list from the preload
1178 * engines preference.
1181 updateCheckboxesFromPreloadEngines_: function() {
1182 // Convert the list into a dictonary for simpler lookup.
1183 var dictionary
= {};
1184 for (var i
= 0; i
< this.preloadEngines_
.length
; i
++) {
1185 dictionary
[this.preloadEngines_
[i
]] = true;
1188 var inputMethodList
= $('language-options-input-method-list');
1189 var checkboxes
= inputMethodList
.querySelectorAll('input');
1190 for (var i
= 0; i
< checkboxes
.length
; i
++) {
1191 if (!checkboxes
[i
].inputMethodId
.match(/^_ext_ime_/))
1192 checkboxes
[i
].checked
= (checkboxes
[i
].inputMethodId
in dictionary
);
1194 var configureButtons
= inputMethodList
.querySelectorAll('button');
1195 for (var i
= 0; i
< configureButtons
.length
; i
++) {
1196 if (!configureButtons
[i
].inputMethodId
.match(/^_ext_ime_/)) {
1197 configureButtons
[i
].hidden
=
1198 !(configureButtons
[i
].inputMethodId
in dictionary
);
1204 * Updates the preload engines preference from the checkboxes in the
1205 * input method list.
1208 updatePreloadEnginesFromCheckboxes_: function() {
1209 this.preloadEngines_
= [];
1210 var inputMethodList
= $('language-options-input-method-list');
1211 var checkboxes
= inputMethodList
.querySelectorAll('input');
1212 for (var i
= 0; i
< checkboxes
.length
; i
++) {
1213 if (!checkboxes
[i
].inputMethodId
.match(/^_ext_ime_/)) {
1214 if (checkboxes
[i
].checked
)
1215 this.preloadEngines_
.push(checkboxes
[i
].inputMethodId
);
1218 var languageOptionsList
= $('language-options-list');
1219 languageOptionsList
.updateDeletable();
1223 * Filters bad preload engines in case bad preload engines are
1224 * stored in the preference. Removes duplicates as well.
1225 * @param {Array} preloadEngines List of preload engines.
1228 filterBadPreloadEngines_: function(preloadEngines
) {
1229 // Convert the list into a dictonary for simpler lookup.
1230 var dictionary
= {};
1231 var list
= loadTimeData
.getValue('inputMethodList');
1232 for (var i
= 0; i
< list
.length
; i
++) {
1233 dictionary
[list
[i
].id
] = true;
1236 var enabledPreloadEngines
= [];
1238 for (var i
= 0; i
< preloadEngines
.length
; i
++) {
1239 // Check if the preload engine is present in the
1240 // dictionary, and not duplicate. Otherwise, skip it.
1241 // Component Extension IME should be handled same as preloadEngines and
1242 // "_comp_" is the special prefix of its ID.
1243 if ((preloadEngines
[i
] in dictionary
&& !(preloadEngines
[i
] in seen
)) ||
1244 /^_comp_/.test(preloadEngines
[i
])) {
1245 enabledPreloadEngines
.push(preloadEngines
[i
]);
1246 seen
[preloadEngines
[i
]] = true;
1249 return enabledPreloadEngines
;
1252 // TODO(kochi): This is an adapted copy from new_tab.js.
1253 // If this will go as final UI, refactor this to share the component with
1254 // new new tab page.
1258 notificationTimeout_
: null,
1261 * Shows notification.
1262 * @param {string} text
1263 * @param {string} actionText
1264 * @param {number=} opt_delay
1267 showNotification_: function(text
, actionText
, opt_delay
) {
1268 var notificationElement
= $('notification');
1269 var actionLink
= notificationElement
.querySelector('.link-color');
1270 var delay
= opt_delay
|| 10000;
1273 window
.clearTimeout(this.notificationTimeout_
);
1274 notificationElement
.classList
.add('show');
1275 document
.body
.classList
.add('notification-shown');
1279 window
.clearTimeout(this.notificationTimeout_
);
1280 notificationElement
.classList
.remove('show');
1281 document
.body
.classList
.remove('notification-shown');
1282 // Prevent tabbing to the hidden link.
1283 actionLink
.tabIndex
= -1;
1284 // Setting tabIndex to -1 only prevents future tabbing to it. If,
1285 // however, the user switches window or a tab and then moves back to
1286 // this tab the element may gain focus. We therefore make sure that we
1287 // blur the element so that the element focus is not restored when
1288 // coming back to this window.
1292 function delayedHide() {
1293 this.notificationTimeout_
= window
.setTimeout(hide
, delay
);
1296 notificationElement
.firstElementChild
.textContent
= text
;
1297 actionLink
.textContent
= actionText
;
1299 actionLink
.onclick
= hide
;
1300 actionLink
.onkeydown = function(e
) {
1301 if (e
.keyIdentifier
== 'Enter') {
1305 notificationElement
.onmouseover
= show
;
1306 notificationElement
.onmouseout
= delayedHide
;
1307 actionLink
.onfocus
= show
;
1308 actionLink
.onblur
= delayedHide
;
1309 // Enable tabbing to the link now that it is shown.
1310 actionLink
.tabIndex
= 0;
1317 * Chrome callback for when the UI language preference is saved.
1318 * @param {string} languageCode The newly selected language to use.
1321 uiLanguageSaved_: function(languageCode
) {
1322 this.prospectiveUiLanguageCode_
= languageCode
;
1324 // If the user is no longer on the same language code, ignore.
1325 if ($('language-options-list').getSelectedLanguageCode() != languageCode
)
1328 // Special case for when a user changes to a different language, and
1329 // changes back to the same language without having restarted Chrome or
1330 // logged in/out of ChromeOS.
1331 if (languageCode
== loadTimeData
.getString('currentUiLanguageCode')) {
1332 this.updateUiLanguageButton_(languageCode
);
1336 // Otherwise, show a notification telling the user that their changes will
1337 // only take effect after restart.
1338 showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
1339 $('language-options-ui-notification-bar')],
1344 * A handler for when dictionary for |languageCode| begins downloading.
1345 * @param {string} languageCode The language of the dictionary that just
1346 * began downloading.
1349 onDictionaryDownloadBegin_: function(languageCode
) {
1350 this.spellcheckDictionaryDownloadStatus_
[languageCode
] =
1351 DOWNLOAD_STATUS
.IN_PROGRESS
;
1354 $('language-options-list').getSelectedLanguageCode()) {
1355 this.updateSpellCheckLanguageControls_(languageCode
);
1360 * A handler for when dictionary for |languageCode| successfully downloaded.
1361 * @param {string} languageCode The language of the dictionary that
1362 * succeeded downloading.
1365 onDictionaryDownloadSuccess_: function(languageCode
) {
1366 delete this.spellcheckDictionaryDownloadStatus_
[languageCode
];
1367 this.spellcheckDictionaryDownloadFailures_
= 0;
1370 $('language-options-list').getSelectedLanguageCode()) {
1371 this.updateSpellCheckLanguageControls_(languageCode
);
1376 * A handler for when dictionary for |languageCode| fails to download.
1377 * @param {string} languageCode The language of the dictionary that failed
1381 onDictionaryDownloadFailure_: function(languageCode
) {
1382 this.spellcheckDictionaryDownloadStatus_
[languageCode
] =
1383 DOWNLOAD_STATUS
.FAILED
;
1384 this.spellcheckDictionaryDownloadFailures_
++;
1387 $('language-options-list').getSelectedLanguageCode()) {
1388 this.updateSpellCheckLanguageControls_(languageCode
);
1393 * Converts the language code for Translation. There are some differences
1394 * between the language set for Translation and that for Accept-Language.
1395 * @param {string} languageCode The language code like 'fr'.
1396 * @return {string} The converted language code.
1399 convertLangCodeForTranslation_: function(languageCode
) {
1400 var tokens
= languageCode
.split('-');
1401 var main
= tokens
[0];
1403 // See also: components/translate/core/browser/common/translate_util.cc
1414 if (main
in synonyms
) {
1415 return synonyms
[main
];
1416 } else if (main
== 'zh') {
1417 // In Translation, general Chinese is not used, and the sub code is
1418 // necessary as a language code for Translate server.
1419 return languageCode
;
1427 * Shows the node at |index| in |nodes|, hides all others.
1428 * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
1429 * @param {number} index The index of |nodes| to show.
1431 function showMutuallyExclusiveNodes(nodes
, index
) {
1432 assert(index
>= 0 && index
< nodes
.length
);
1433 for (var i
= 0; i
< nodes
.length
; ++i
) {
1434 assert(nodes
[i
] instanceof HTMLElement
); // TODO(dbeam): Ignore null?
1435 nodes
[i
].hidden
= i
!= index
;
1439 LanguageOptions
.uiLanguageSaved = function(languageCode
) {
1440 LanguageOptions
.getInstance().uiLanguageSaved_(languageCode
);
1443 LanguageOptions
.onDictionaryDownloadBegin = function(languageCode
) {
1444 LanguageOptions
.getInstance().onDictionaryDownloadBegin_(languageCode
);
1447 LanguageOptions
.onDictionaryDownloadSuccess = function(languageCode
) {
1448 LanguageOptions
.getInstance().onDictionaryDownloadSuccess_(languageCode
);
1451 LanguageOptions
.onDictionaryDownloadFailure = function(languageCode
) {
1452 LanguageOptions
.getInstance().onDictionaryDownloadFailure_(languageCode
);
1457 LanguageOptions
: LanguageOptions