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');
210 $('language-options-add-button').onclick
= onclick
.bind(this);
213 // Set up the button for editing custom spelling dictionary.
214 $('edit-dictionary-button').onclick = function(e
) {
215 PageManager
.showPageByName('editDictionary');
217 $('dictionary-download-retry-button').onclick = function(e
) {
218 chrome
.send('retryDictionaryDownload');
222 // Listen to add language dialog ok button.
223 $('add-language-overlay-ok-button').addEventListener(
224 'click', this.handleAddLanguageOkButtonClick_
.bind(this));
226 if (!cr
.isChromeOS
) {
227 // Show experimental features if enabled.
228 if (loadTimeData
.getBoolean('enableSpellingAutoCorrect'))
229 $('auto-spell-correction-option').hidden
= false;
231 // Handle spell check enable/disable.
233 Preferences
.getInstance().addEventListener(
234 ENABLE_SPELL_CHECK_PREF
,
235 this.updateEnableSpellCheck_
.bind(this));
239 // Handle clicks on "Use this language for spell checking" button.
241 var spellCheckLanguageButton
= getRequiredElement(
242 'language-options-spell-check-language-button');
243 spellCheckLanguageButton
.addEventListener(
245 this.handleSpellCheckLanguageButtonClick_
.bind(this));
249 $('language-options-ui-restart-button').onclick = function() {
250 chrome
.send('uiLanguageRestart');
254 $('language-confirm').onclick
=
255 PageManager
.closeOverlay
.bind(PageManager
);
257 // Public session users cannot change the locale.
258 if (cr
.isChromeOS
&& UIAccountTweaks
.loggedInAsPublicAccount())
259 $('language-options-ui-language-section').hidden
= true;
260 PageManager
.closeOverlay
.bind(PageManager
);
264 * Initializes the input method list.
266 initializeInputMethodList_: function() {
267 var inputMethodList
= $('language-options-input-method-list');
268 var inputMethodPrototype
= $('language-options-input-method-template');
270 // Add all input methods, but make all of them invisible here. We'll
271 // change the visibility in handleLanguageOptionsListChange_() based
272 // on the selected language. Note that we only have less than 100
273 // input methods, so creating DOM nodes at once here should be ok.
274 this.appendInputMethodElement_(loadTimeData
.getValue('inputMethodList'));
275 this.appendInputMethodElement_(loadTimeData
.getValue('extensionImeList'));
276 this.appendComponentExtensionIme_(
277 loadTimeData
.getValue('componentExtensionImeList'));
279 // Listen to pref change once the input method list is initialized.
280 Preferences
.getInstance().addEventListener(
281 PRELOAD_ENGINES_PREF
,
282 this.handlePreloadEnginesPrefChange_
.bind(this));
283 Preferences
.getInstance().addEventListener(
284 ENABLED_EXTENSION_IME_PREF
,
285 this.handleEnabledExtensionsPrefChange_
.bind(this));
289 * Appends input method lists based on component extension ime list.
290 * @param {!Array} componentExtensionImeList A list of input method
294 appendComponentExtensionIme_: function(componentExtensionImeList
) {
295 this.appendInputMethodElement_(componentExtensionImeList
);
297 for (var i
= 0; i
< componentExtensionImeList
.length
; i
++) {
298 var inputMethod
= componentExtensionImeList
[i
];
299 for (var languageCode
in inputMethod
.languageCodeSet
) {
300 if (languageCode
in this.languageCodeToInputMethodIdsMap_
) {
301 this.languageCodeToInputMethodIdsMap_
[languageCode
].push(
304 this.languageCodeToInputMethodIdsMap_
[languageCode
] =
312 * Appends input methods into input method list.
313 * @param {!Array} inputMethods A list of input method descriptors.
316 appendInputMethodElement_: function(inputMethods
) {
317 var inputMethodList
= $('language-options-input-method-list');
318 var inputMethodTemplate
= $('language-options-input-method-template');
320 for (var i
= 0; i
< inputMethods
.length
; i
++) {
321 var inputMethod
= inputMethods
[i
];
322 var element
= inputMethodTemplate
.cloneNode(true);
324 element
.languageCodeSet
= inputMethod
.languageCodeSet
;
326 var input
= element
.querySelector('input');
327 input
.inputMethodId
= inputMethod
.id
;
328 input
.imeProvider
= inputMethod
.extensionName
;
329 var span
= element
.querySelector('span');
330 span
.textContent
= inputMethod
.displayName
;
332 if (inputMethod
.optionsPage
) {
333 var button
= document
.createElement('button');
334 button
.textContent
= loadTimeData
.getString('configure');
335 button
.inputMethodId
= inputMethod
.id
;
336 button
.onclick = function(inputMethodId
, e
) {
337 chrome
.send('inputMethodOptionsOpen', [inputMethodId
]);
339 button
.onclick
= button
.onclick
.bind(this, inputMethod
.id
);
340 element
.appendChild(button
);
343 // Listen to user clicks.
344 input
.addEventListener('click',
345 this.handleCheckboxClick_
.bind(this));
346 inputMethodList
.appendChild(element
);
351 * Adds a language to the preference 'translate_blocked_languages'. If
352 * |langCode| is already added, nothing happens. |langCode| is converted
353 * to a Translate language synonym before added.
354 * @param {string} langCode A language code like 'en'
357 addBlockedLanguage_: function(langCode
) {
358 langCode
= this.convertLangCodeForTranslation_(langCode
);
359 if (this.translateBlockedLanguages_
.indexOf(langCode
) == -1) {
360 this.translateBlockedLanguages_
.push(langCode
);
361 Preferences
.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF
,
362 this.translateBlockedLanguages_
, true);
367 * Removes a language from the preference 'translate_blocked_languages'.
368 * If |langCode| doesn't exist in the preference, nothing happens.
369 * |langCode| is converted to a Translate language synonym before removed.
370 * @param {string} langCode A language code like 'en'
373 removeBlockedLanguage_: function(langCode
) {
374 langCode
= this.convertLangCodeForTranslation_(langCode
);
375 if (this.translateBlockedLanguages_
.indexOf(langCode
) != -1) {
376 this.translateBlockedLanguages_
=
377 this.translateBlockedLanguages_
.filter(
378 function(langCodeNotTranslated
) {
379 return langCodeNotTranslated
!= langCode
;
381 Preferences
.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF
,
382 this.translateBlockedLanguages_
, true);
387 * Handles Page's visible property change event.
388 * @param {Event} e Property change event.
391 handleVisibleChange_: function(e
) {
393 $('language-options-list').redraw();
394 chrome
.send('languageOptionsOpen');
399 * Handles languageOptionsList's change event.
400 * @param {Event} e Change event.
403 handleLanguageOptionsListChange_: function(e
) {
404 var languageOptionsList
= $('language-options-list');
405 var languageCode
= languageOptionsList
.getSelectedLanguageCode();
407 // If there's no selection, just return.
411 // Select the language if it's specified in the URL hash (ex. lang=ja).
412 // Used for automated testing.
413 var match
= document
.location
.hash
.match(/\blang=([\w-]+)/);
415 var specifiedLanguageCode
= match
[1];
416 if (languageOptionsList
.selectLanguageByCode(specifiedLanguageCode
)) {
417 languageCode
= specifiedLanguageCode
;
421 this.updateOfferToTranslateCheckbox_(languageCode
);
423 if (cr
.isWindows
|| cr
.isChromeOS
)
424 this.updateUiLanguageButton_(languageCode
);
426 this.updateSelectedLanguageName_(languageCode
);
429 this.updateSpellCheckLanguageButton_(languageCode
);
432 this.updateInputMethodList_(languageCode
);
434 this.updateLanguageListInAddLanguageOverlay_();
438 * Handles languageOptionsList's save event.
439 * @param {Event} e Save event.
442 handleLanguageOptionsListSave_: function(e
) {
444 // Sort the preload engines per the saved languages before save.
445 this.preloadEngines_
= this.sortPreloadEngines_(this.preloadEngines_
);
446 this.savePreloadEnginesPref_();
451 * Sorts preloadEngines_ by languageOptionsList's order.
452 * @param {Array} preloadEngines List of preload engines.
453 * @return {Array} Returns sorted preloadEngines.
456 sortPreloadEngines_: function(preloadEngines
) {
457 // For instance, suppose we have two languages and associated input
463 // The preloadEngines preference should look like "hangul,pinyin".
464 // If the user reverse the order, the preference should be reorderd
465 // to "pinyin,hangul".
466 var languageOptionsList
= $('language-options-list');
467 var languageCodes
= languageOptionsList
.getLanguageCodes();
469 // Convert the list into a dictonary for simpler lookup.
470 var preloadEngineSet
= {};
471 for (var i
= 0; i
< preloadEngines
.length
; i
++) {
472 preloadEngineSet
[preloadEngines
[i
]] = true;
475 // Create the new preload engine list per the language codes.
476 var newPreloadEngines
= [];
477 for (var i
= 0; i
< languageCodes
.length
; i
++) {
478 var languageCode
= languageCodes
[i
];
479 var inputMethodIds
= this.languageCodeToInputMethodIdsMap_
[
484 // Check if we have active input methods associated with the language.
485 for (var j
= 0; j
< inputMethodIds
.length
; j
++) {
486 var inputMethodId
= inputMethodIds
[j
];
487 if (inputMethodId
in preloadEngineSet
) {
488 // If we have, add it to the new engine list.
489 newPreloadEngines
.push(inputMethodId
);
490 // And delete it from the set. This is necessary as one input
491 // method can be associated with more than one language thus
492 // we should avoid having duplicates in the new list.
493 delete preloadEngineSet
[inputMethodId
];
498 return newPreloadEngines
;
502 * Initializes the map of language code to input method IDs.
505 initializeLanguageCodeToInputMethodIdsMap_: function() {
506 var inputMethodList
= loadTimeData
.getValue('inputMethodList');
507 for (var i
= 0; i
< inputMethodList
.length
; i
++) {
508 var inputMethod
= inputMethodList
[i
];
509 for (var languageCode
in inputMethod
.languageCodeSet
) {
510 if (languageCode
in this.languageCodeToInputMethodIdsMap_
) {
511 this.languageCodeToInputMethodIdsMap_
[languageCode
].push(
514 this.languageCodeToInputMethodIdsMap_
[languageCode
] =
522 * Updates the currently selected language name.
523 * @param {string} languageCode Language code (ex. "fr").
526 updateSelectedLanguageName_: function(languageCode
) {
527 var languageInfo
= LanguageList
.getLanguageInfoFromLanguageCode(
529 var languageDisplayName
= languageInfo
.displayName
;
530 var languageNativeDisplayName
= languageInfo
.nativeDisplayName
;
531 var textDirection
= languageInfo
.textDirection
;
533 // If the native name is different, add it.
534 if (languageDisplayName
!= languageNativeDisplayName
) {
535 languageDisplayName
+= ' - ' + languageNativeDisplayName
;
538 // Update the currently selected language name.
539 var languageName
= $('language-options-language-name');
540 languageName
.textContent
= languageDisplayName
;
541 languageName
.dir
= textDirection
;
545 * Updates the UI language button.
546 * @param {string} languageCode Language code (ex. "fr").
549 updateUiLanguageButton_: function(languageCode
) {
550 var uiLanguageButton
= $('language-options-ui-language-button');
551 var uiLanguageMessage
= $('language-options-ui-language-message');
552 var uiLanguageNotification
= $('language-options-ui-notification-bar');
554 // Remove the event listener and add it back if useful.
555 uiLanguageButton
.onclick
= null;
557 // Unhide the language button every time, as it could've been previously
558 // hidden by a language change.
559 uiLanguageButton
.hidden
= false;
561 // Hide the controlled setting indicator.
562 var uiLanguageIndicator
= document
.querySelector(
563 '.language-options-contents .controlled-setting-indicator');
564 uiLanguageIndicator
.removeAttribute('controlled-by');
566 if (languageCode
== this.prospectiveUiLanguageCode_
) {
567 uiLanguageMessage
.textContent
=
568 loadTimeData
.getString('isDisplayedInThisLanguage');
569 showMutuallyExclusiveNodes(
570 [uiLanguageButton
, uiLanguageMessage
, uiLanguageNotification
], 1);
571 } else if (languageCode
in loadTimeData
.getValue('uiLanguageCodeSet')) {
572 if (cr
.isChromeOS
&& UIAccountTweaks
.loggedInAsGuest()) {
573 // In the guest mode for ChromeOS, changing UI language does not make
574 // sense because it does not take effect after browser restart.
575 uiLanguageButton
.hidden
= true;
576 uiLanguageMessage
.hidden
= true;
578 uiLanguageButton
.textContent
=
579 loadTimeData
.getString('displayInThisLanguage');
581 if (loadTimeData
.valueExists('secondaryUser') &&
582 loadTimeData
.getBoolean('secondaryUser')) {
583 uiLanguageButton
.disabled
= true;
584 uiLanguageIndicator
.setAttribute('controlled-by', 'shared');
586 uiLanguageButton
.onclick = function(e
) {
587 chrome
.send('uiLanguageChange', [languageCode
]);
590 showMutuallyExclusiveNodes(
591 [uiLanguageButton
, uiLanguageMessage
, uiLanguageNotification
], 0);
594 uiLanguageMessage
.textContent
=
595 loadTimeData
.getString('cannotBeDisplayedInThisLanguage');
596 showMutuallyExclusiveNodes(
597 [uiLanguageButton
, uiLanguageMessage
, uiLanguageNotification
], 1);
602 * Updates the spell check language button.
603 * @param {string} languageCode Language code (ex. "fr").
606 updateSpellCheckLanguageButton_: function(languageCode
) {
607 var spellCheckLanguageSection
= $('language-options-spellcheck');
608 var spellCheckLanguageButton
=
609 $('language-options-spell-check-language-button');
610 var spellCheckLanguageMessage
=
611 $('language-options-spell-check-language-message');
612 var dictionaryDownloadInProgress
=
613 $('language-options-dictionary-downloading-message');
614 var dictionaryDownloadFailed
=
615 $('language-options-dictionary-download-failed-message');
616 var dictionaryDownloadFailHelp
=
617 $('language-options-dictionary-download-fail-help-message');
618 spellCheckLanguageSection
.hidden
= false;
619 spellCheckLanguageMessage
.hidden
= true;
620 spellCheckLanguageButton
.hidden
= true;
621 dictionaryDownloadInProgress
.hidden
= true;
622 dictionaryDownloadFailed
.hidden
= true;
623 dictionaryDownloadFailHelp
.hidden
= true;
625 if (languageCode
== this.spellCheckDictionary_
) {
626 if (!(languageCode
in this.spellcheckDictionaryDownloadStatus_
)) {
627 spellCheckLanguageMessage
.textContent
=
628 loadTimeData
.getString('isUsedForSpellChecking');
629 showMutuallyExclusiveNodes(
630 [spellCheckLanguageButton
, spellCheckLanguageMessage
], 1);
631 } else if (this.spellcheckDictionaryDownloadStatus_
[languageCode
] ==
632 DOWNLOAD_STATUS
.IN_PROGRESS
) {
633 dictionaryDownloadInProgress
.hidden
= false;
634 } else if (this.spellcheckDictionaryDownloadStatus_
[languageCode
] ==
635 DOWNLOAD_STATUS
.FAILED
) {
636 spellCheckLanguageSection
.hidden
= true;
637 dictionaryDownloadFailed
.hidden
= false;
638 if (this.spellcheckDictionaryDownloadFailures_
> 1)
639 dictionaryDownloadFailHelp
.hidden
= false;
641 } else if (languageCode
in
642 loadTimeData
.getValue('spellCheckLanguageCodeSet')) {
643 spellCheckLanguageButton
.textContent
=
644 loadTimeData
.getString('useThisForSpellChecking');
645 showMutuallyExclusiveNodes(
646 [spellCheckLanguageButton
, spellCheckLanguageMessage
], 0);
647 spellCheckLanguageButton
.languageCode
= languageCode
;
648 } else if (!languageCode
) {
649 spellCheckLanguageButton
.hidden
= true;
650 spellCheckLanguageMessage
.hidden
= true;
652 spellCheckLanguageMessage
.textContent
=
653 loadTimeData
.getString('cannotBeUsedForSpellChecking');
654 showMutuallyExclusiveNodes(
655 [spellCheckLanguageButton
, spellCheckLanguageMessage
], 1);
660 * Updates the checkbox for stopping translation.
661 * @param {string} languageCode Language code (ex. "fr").
664 updateOfferToTranslateCheckbox_: function(languageCode
) {
665 var div
= $('language-options-offer-to-translate');
667 // Translation server supports Chinese (Transitional) and Chinese
668 // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
669 // show this preference when general Chinese is selected.
670 if (languageCode
!= 'zh') {
677 var offerToTranslate
= div
.querySelector('div');
678 var cannotTranslate
= $('cannot-translate-in-this-language');
679 var nodes
= [offerToTranslate
, cannotTranslate
];
681 var convertedLangCode
= this.convertLangCodeForTranslation_(languageCode
);
682 if (this.translateSupportedLanguages_
.indexOf(convertedLangCode
) != -1) {
683 showMutuallyExclusiveNodes(nodes
, 0);
685 showMutuallyExclusiveNodes(nodes
, 1);
689 var checkbox
= $('offer-to-translate-in-this-language');
691 if (!this.enableTranslate_
) {
692 checkbox
.disabled
= true;
693 checkbox
.checked
= false;
697 // If the language corresponds to the default target language (in most
698 // cases, the user's locale language), "Offer to translate" checkbox
699 // should be always unchecked.
700 var defaultTargetLanguage
=
701 loadTimeData
.getString('defaultTargetLanguage');
702 if (convertedLangCode
== defaultTargetLanguage
) {
703 checkbox
.disabled
= true;
704 checkbox
.checked
= false;
708 checkbox
.disabled
= false;
710 var blockedLanguages
= this.translateBlockedLanguages_
;
711 var checked
= blockedLanguages
.indexOf(convertedLangCode
) == -1;
712 checkbox
.checked
= checked
;
716 * Updates the input method list.
717 * @param {string} languageCode Language code (ex. "fr").
720 updateInputMethodList_: function(languageCode
) {
721 // Give one of the checkboxes or buttons focus, if it's specified in the
722 // URL hash (ex. focus=mozc). Used for automated testing.
723 var focusInputMethodId
= -1;
724 var match
= document
.location
.hash
.match(/\bfocus=([\w:-]+)\b/);
726 focusInputMethodId
= match
[1];
728 // Change the visibility of the input method list. Input methods that
729 // matches |languageCode| will become visible.
730 var inputMethodList
= $('language-options-input-method-list');
731 var methods
= inputMethodList
.querySelectorAll('.input-method');
732 for (var i
= 0; i
< methods
.length
; i
++) {
733 var method
= methods
[i
];
734 if (languageCode
in method
.languageCodeSet
) {
735 method
.hidden
= false;
736 var input
= method
.querySelector('input');
737 // Give it focus if the ID matches.
738 if (input
.inputMethodId
== focusInputMethodId
) {
742 method
.hidden
= true;
746 $('language-options-input-method-none').hidden
=
747 (languageCode
in this.languageCodeToInputMethodIdsMap_
);
749 if (focusInputMethodId
== 'add') {
750 $('language-options-add-button').focus();
755 * Updates the language list in the add language overlay.
756 * @param {string} languageCode Language code (ex. "fr").
759 updateLanguageListInAddLanguageOverlay_: function(languageCode
) {
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
= e
.target
;
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
);
854 * Updates active IMEs based on change in state of a checkbox for an input
856 * @param {!Element} checkbox Updated checkbox element.
859 handleCheckboxUpdate_: function(checkbox
) {
860 if (checkbox
.inputMethodId
.match(/^_ext_ime_/)) {
861 this.updateEnabledExtensionsFromCheckboxes_();
862 this.saveEnabledExtensionPref_();
865 if (this.preloadEngines_
.length
== 1 && !checkbox
.checked
) {
866 // Don't allow disabling the last input method.
867 this.showNotification_(
868 loadTimeData
.getString('pleaseAddAnotherInputMethod'),
869 loadTimeData
.getString('okButton'));
870 checkbox
.checked
= true;
873 if (checkbox
.checked
) {
874 chrome
.send('inputMethodEnable', [checkbox
.inputMethodId
]);
876 chrome
.send('inputMethodDisable', [checkbox
.inputMethodId
]);
878 this.updatePreloadEnginesFromCheckboxes_();
879 this.preloadEngines_
= this.sortPreloadEngines_(this.preloadEngines_
);
880 this.savePreloadEnginesPref_();
884 * Handles clicks on the "OK" button of the "Add language" dialog.
885 * @param {Event} e Click event.
888 handleAddLanguageOkButtonClick_: function(e
) {
889 var languagesSelect
= $('add-language-overlay-language-list');
890 var selectedIndex
= languagesSelect
.selectedIndex
;
891 if (selectedIndex
>= 0) {
892 var selection
= languagesSelect
.options
[selectedIndex
];
893 var langCode
= String(selection
.value
);
894 $('language-options-list').addLanguage(langCode
);
895 this.addBlockedLanguage_(langCode
);
896 PageManager
.closeOverlay();
901 * Checks if languageCode is deletable or not.
902 * @param {string} languageCode the languageCode to check for deletability.
904 languageIsDeletable: function(languageCode
) {
905 // Don't allow removing the language if it's a UI language.
906 if (languageCode
== this.prospectiveUiLanguageCode_
)
908 return (!cr
.isChromeOS
||
909 this.canDeleteLanguage_(languageCode
));
913 * Handles browse.enable_spellchecking change.
914 * @param {Event} e Change event.
917 updateEnableSpellCheck_: function(e
) {
918 var value
= !$('enable-spell-check').checked
;
919 $('language-options-spell-check-language-button').disabled
= value
;
921 $('edit-dictionary-button').hidden
= value
;
925 * Handles translateBlockedLanguagesPref change.
926 * @param {Event} e Change event.
929 handleTranslateBlockedLanguagesPrefChange_: function(e
) {
930 this.translateBlockedLanguages_
= e
.value
.value
;
931 this.updateOfferToTranslateCheckbox_(
932 $('language-options-list').getSelectedLanguageCode());
936 * Handles spellCheckDictionaryPref change.
937 * @param {Event} e Change event.
940 handleSpellCheckDictionaryPrefChange_: function(e
) {
941 var languageCode
= e
.value
.value
;
942 this.spellCheckDictionary_
= languageCode
;
944 this.updateSpellCheckLanguageButton_(
945 $('language-options-list').getSelectedLanguageCode());
950 * Handles translate.enabled change.
951 * @param {Event} e Change event.
954 handleEnableTranslatePrefChange_: function(e
) {
955 var enabled
= e
.value
.value
;
956 this.enableTranslate_
= enabled
;
957 this.updateOfferToTranslateCheckbox_(
958 $('language-options-list').getSelectedLanguageCode());
962 * Handles spellCheckLanguageButton click.
963 * @param {Event} e Click event.
966 handleSpellCheckLanguageButtonClick_: function(e
) {
967 var languageCode
= e
.target
.languageCode
;
968 // Save the preference.
969 Preferences
.setStringPref(SPELL_CHECK_DICTIONARY_PREF
,
971 chrome
.send('spellCheckLanguageChange', [languageCode
]);
975 * Checks whether it's possible to remove the language specified by
976 * languageCode and returns true if possible. This function returns false
977 * if the removal causes the number of preload engines to be zero.
979 * @param {string} languageCode Language code (ex. "fr").
980 * @return {boolean} Returns true on success.
983 canDeleteLanguage_: function(languageCode
) {
984 // First create the set of engines to be removed from input methods
985 // associated with the language code.
986 var enginesToBeRemovedSet
= {};
987 var inputMethodIds
= this.languageCodeToInputMethodIdsMap_
[languageCode
];
989 // If this language doesn't have any input methods, it can be deleted.
993 for (var i
= 0; i
< inputMethodIds
.length
; i
++) {
994 enginesToBeRemovedSet
[inputMethodIds
[i
]] = true;
997 // Then eliminate engines that are also used for other active languages.
998 // For instance, if "xkb:us::eng" is used for both English and Filipino.
999 var languageCodes
= $('language-options-list').getLanguageCodes();
1000 for (var i
= 0; i
< languageCodes
.length
; i
++) {
1001 // Skip the target language code.
1002 if (languageCodes
[i
] == languageCode
) {
1005 // Check if input methods used in this language are included in
1006 // enginesToBeRemovedSet. If so, eliminate these from the set, so
1007 // we don't remove this time.
1008 var inputMethodIdsForAnotherLanguage
=
1009 this.languageCodeToInputMethodIdsMap_
[languageCodes
[i
]];
1010 if (!inputMethodIdsForAnotherLanguage
)
1013 for (var j
= 0; j
< inputMethodIdsForAnotherLanguage
.length
; j
++) {
1014 var inputMethodId
= inputMethodIdsForAnotherLanguage
[j
];
1015 if (inputMethodId
in enginesToBeRemovedSet
) {
1016 delete enginesToBeRemovedSet
[inputMethodId
];
1021 // Update the preload engine list with the to-be-removed set.
1022 var newPreloadEngines
= [];
1023 for (var i
= 0; i
< this.preloadEngines_
.length
; i
++) {
1024 if (!(this.preloadEngines_
[i
] in enginesToBeRemovedSet
)) {
1025 newPreloadEngines
.push(this.preloadEngines_
[i
]);
1028 // Don't allow this operation if it causes the number of preload
1029 // engines to be zero.
1030 return (newPreloadEngines
.length
> 0);
1034 * Saves the enabled extension preference.
1037 saveEnabledExtensionPref_: function() {
1038 Preferences
.setStringPref(ENABLED_EXTENSION_IME_PREF
,
1039 this.enabledExtensionImes_
.join(','), true);
1043 * Updates the checkboxes in the input method list from the enabled
1044 * extensions preference.
1047 updateCheckboxesFromEnabledExtensions_: function() {
1048 // Convert the list into a dictonary for simpler lookup.
1049 var dictionary
= {};
1050 for (var i
= 0; i
< this.enabledExtensionImes_
.length
; i
++)
1051 dictionary
[this.enabledExtensionImes_
[i
]] = true;
1053 var inputMethodList
= $('language-options-input-method-list');
1054 var checkboxes
= inputMethodList
.querySelectorAll('input');
1055 for (var i
= 0; i
< checkboxes
.length
; i
++) {
1056 if (checkboxes
[i
].inputMethodId
.match(/^_ext_ime_/))
1057 checkboxes
[i
].checked
= (checkboxes
[i
].inputMethodId
in dictionary
);
1059 var configureButtons
= inputMethodList
.querySelectorAll('button');
1060 for (var i
= 0; i
< configureButtons
.length
; i
++) {
1061 if (configureButtons
[i
].inputMethodId
.match(/^_ext_ime_/)) {
1062 configureButtons
[i
].hidden
=
1063 !(configureButtons
[i
].inputMethodId
in dictionary
);
1069 * Updates the enabled extensions preference from the checkboxes in the
1070 * input method list.
1073 updateEnabledExtensionsFromCheckboxes_: function() {
1074 this.enabledExtensionImes_
= [];
1075 var inputMethodList
= $('language-options-input-method-list');
1076 var checkboxes
= inputMethodList
.querySelectorAll('input');
1077 for (var i
= 0; i
< checkboxes
.length
; i
++) {
1078 if (checkboxes
[i
].inputMethodId
.match(/^_ext_ime_/)) {
1079 if (checkboxes
[i
].checked
)
1080 this.enabledExtensionImes_
.push(checkboxes
[i
].inputMethodId
);
1086 * Saves the preload engines preference.
1089 savePreloadEnginesPref_: function() {
1090 Preferences
.setStringPref(PRELOAD_ENGINES_PREF
,
1091 this.preloadEngines_
.join(','), true);
1095 * Updates the checkboxes in the input method list from the preload
1096 * engines preference.
1099 updateCheckboxesFromPreloadEngines_: function() {
1100 // Convert the list into a dictonary for simpler lookup.
1101 var dictionary
= {};
1102 for (var i
= 0; i
< this.preloadEngines_
.length
; i
++) {
1103 dictionary
[this.preloadEngines_
[i
]] = true;
1106 var inputMethodList
= $('language-options-input-method-list');
1107 var checkboxes
= inputMethodList
.querySelectorAll('input');
1108 for (var i
= 0; i
< checkboxes
.length
; i
++) {
1109 if (!checkboxes
[i
].inputMethodId
.match(/^_ext_ime_/))
1110 checkboxes
[i
].checked
= (checkboxes
[i
].inputMethodId
in dictionary
);
1112 var configureButtons
= inputMethodList
.querySelectorAll('button');
1113 for (var i
= 0; i
< configureButtons
.length
; i
++) {
1114 if (!configureButtons
[i
].inputMethodId
.match(/^_ext_ime_/)) {
1115 configureButtons
[i
].hidden
=
1116 !(configureButtons
[i
].inputMethodId
in dictionary
);
1122 * Updates the preload engines preference from the checkboxes in the
1123 * input method list.
1126 updatePreloadEnginesFromCheckboxes_: function() {
1127 this.preloadEngines_
= [];
1128 var inputMethodList
= $('language-options-input-method-list');
1129 var checkboxes
= inputMethodList
.querySelectorAll('input');
1130 for (var i
= 0; i
< checkboxes
.length
; i
++) {
1131 if (!checkboxes
[i
].inputMethodId
.match(/^_ext_ime_/)) {
1132 if (checkboxes
[i
].checked
)
1133 this.preloadEngines_
.push(checkboxes
[i
].inputMethodId
);
1136 var languageOptionsList
= $('language-options-list');
1137 languageOptionsList
.updateDeletable();
1141 * Filters bad preload engines in case bad preload engines are
1142 * stored in the preference. Removes duplicates as well.
1143 * @param {Array} preloadEngines List of preload engines.
1146 filterBadPreloadEngines_: function(preloadEngines
) {
1147 // Convert the list into a dictonary for simpler lookup.
1148 var dictionary
= {};
1149 var list
= loadTimeData
.getValue('inputMethodList');
1150 for (var i
= 0; i
< list
.length
; i
++) {
1151 dictionary
[list
[i
].id
] = true;
1154 var enabledPreloadEngines
= [];
1156 for (var i
= 0; i
< preloadEngines
.length
; i
++) {
1157 // Check if the preload engine is present in the
1158 // dictionary, and not duplicate. Otherwise, skip it.
1159 // Component Extension IME should be handled same as preloadEngines and
1160 // "_comp_" is the special prefix of its ID.
1161 if ((preloadEngines
[i
] in dictionary
&& !(preloadEngines
[i
] in seen
)) ||
1162 /^_comp_/.test(preloadEngines
[i
])) {
1163 enabledPreloadEngines
.push(preloadEngines
[i
]);
1164 seen
[preloadEngines
[i
]] = true;
1167 return enabledPreloadEngines
;
1170 // TODO(kochi): This is an adapted copy from new_tab.js.
1171 // If this will go as final UI, refactor this to share the component with
1172 // new new tab page.
1174 * Shows notification
1177 notificationTimeout_
: null,
1178 showNotification_: function(text
, actionText
, opt_delay
) {
1179 var notificationElement
= $('notification');
1180 var actionLink
= notificationElement
.querySelector('.link-color');
1181 var delay
= opt_delay
|| 10000;
1184 window
.clearTimeout(this.notificationTimeout_
);
1185 notificationElement
.classList
.add('show');
1186 document
.body
.classList
.add('notification-shown');
1190 window
.clearTimeout(this.notificationTimeout_
);
1191 notificationElement
.classList
.remove('show');
1192 document
.body
.classList
.remove('notification-shown');
1193 // Prevent tabbing to the hidden link.
1194 actionLink
.tabIndex
= -1;
1195 // Setting tabIndex to -1 only prevents future tabbing to it. If,
1196 // however, the user switches window or a tab and then moves back to
1197 // this tab the element may gain focus. We therefore make sure that we
1198 // blur the element so that the element focus is not restored when
1199 // coming back to this window.
1203 function delayedHide() {
1204 this.notificationTimeout_
= window
.setTimeout(hide
, delay
);
1207 notificationElement
.firstElementChild
.textContent
= text
;
1208 actionLink
.textContent
= actionText
;
1210 actionLink
.onclick
= hide
;
1211 actionLink
.onkeydown = function(e
) {
1212 if (e
.keyIdentifier
== 'Enter') {
1216 notificationElement
.onmouseover
= show
;
1217 notificationElement
.onmouseout
= delayedHide
;
1218 actionLink
.onfocus
= show
;
1219 actionLink
.onblur
= delayedHide
;
1220 // Enable tabbing to the link now that it is shown.
1221 actionLink
.tabIndex
= 0;
1228 * Chrome callback for when the UI language preference is saved.
1229 * @param {string} languageCode The newly selected language to use.
1232 uiLanguageSaved_: function(languageCode
) {
1233 this.prospectiveUiLanguageCode_
= languageCode
;
1235 // If the user is no longer on the same language code, ignore.
1236 if ($('language-options-list').getSelectedLanguageCode() != languageCode
)
1239 // Special case for when a user changes to a different language, and
1240 // changes back to the same language without having restarted Chrome or
1241 // logged in/out of ChromeOS.
1242 if (languageCode
== loadTimeData
.getString('currentUiLanguageCode')) {
1243 this.updateUiLanguageButton_(languageCode
);
1247 // Otherwise, show a notification telling the user that their changes will
1248 // only take effect after restart.
1249 showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
1250 $('language-options-ui-notification-bar')],
1255 * A handler for when dictionary for |languageCode| begins downloading.
1256 * @param {string} languageCode The language of the dictionary that just
1257 * began downloading.
1260 onDictionaryDownloadBegin_: function(languageCode
) {
1261 this.spellcheckDictionaryDownloadStatus_
[languageCode
] =
1262 DOWNLOAD_STATUS
.IN_PROGRESS
;
1265 $('language-options-list').getSelectedLanguageCode()) {
1266 this.updateSpellCheckLanguageButton_(languageCode
);
1271 * A handler for when dictionary for |languageCode| successfully downloaded.
1272 * @param {string} languageCode The language of the dictionary that
1273 * succeeded downloading.
1276 onDictionaryDownloadSuccess_: function(languageCode
) {
1277 delete this.spellcheckDictionaryDownloadStatus_
[languageCode
];
1278 this.spellcheckDictionaryDownloadFailures_
= 0;
1281 $('language-options-list').getSelectedLanguageCode()) {
1282 this.updateSpellCheckLanguageButton_(languageCode
);
1287 * A handler for when dictionary for |languageCode| fails to download.
1288 * @param {string} languageCode The language of the dictionary that failed
1292 onDictionaryDownloadFailure_: function(languageCode
) {
1293 this.spellcheckDictionaryDownloadStatus_
[languageCode
] =
1294 DOWNLOAD_STATUS
.FAILED
;
1295 this.spellcheckDictionaryDownloadFailures_
++;
1298 $('language-options-list').getSelectedLanguageCode()) {
1299 this.updateSpellCheckLanguageButton_(languageCode
);
1304 * Converts the language code for Translation. There are some differences
1305 * between the language set for Translation and that for Accept-Language.
1306 * @param {string} languageCode The language code like 'fr'.
1307 * @return {string} The converted language code.
1310 convertLangCodeForTranslation_: function(languageCode
) {
1311 var tokens
= languageCode
.split('-');
1312 var main
= tokens
[0];
1314 // See also: chrome/renderer/translate/translate_helper.cc.
1322 if (main
in synonyms
) {
1323 return synonyms
[main
];
1324 } else if (main
== 'zh') {
1325 // In Translation, general Chinese is not used, and the sub code is
1326 // necessary as a language code for Translate server.
1327 return languageCode
;
1335 * Shows the node at |index| in |nodes|, hides all others.
1336 * @param {Array.<HTMLElement>} nodes The nodes to be shown or hidden.
1337 * @param {number} index The index of |nodes| to show.
1339 function showMutuallyExclusiveNodes(nodes
, index
) {
1340 assert(index
>= 0 && index
< nodes
.length
);
1341 for (var i
= 0; i
< nodes
.length
; ++i
) {
1342 assert(nodes
[i
] instanceof HTMLElement
); // TODO(dbeam): Ignore null?
1343 nodes
[i
].hidden
= i
!= index
;
1347 LanguageOptions
.uiLanguageSaved = function(languageCode
) {
1348 LanguageOptions
.getInstance().uiLanguageSaved_(languageCode
);
1351 LanguageOptions
.onDictionaryDownloadBegin = function(languageCode
) {
1352 LanguageOptions
.getInstance().onDictionaryDownloadBegin_(languageCode
);
1355 LanguageOptions
.onDictionaryDownloadSuccess = function(languageCode
) {
1356 LanguageOptions
.getInstance().onDictionaryDownloadSuccess_(languageCode
);
1359 LanguageOptions
.onDictionaryDownloadFailure = function(languageCode
) {
1360 LanguageOptions
.getInstance().onDictionaryDownloadFailure_(languageCode
);
1365 LanguageOptions
: LanguageOptions