cygprofile: increase timeouts to allow showing web contents
[chromium-blink-merge.git] / chrome / browser / resources / options / language_options.js
blob088f64fdbfa0b6fb51c7b32debb804223a1f2196
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;
15   /**
16    * Spell check dictionary download status.
17    * @type {Enum}
18    */
19   /** @const*/ var DOWNLOAD_STATUS = {
20     IN_PROGRESS: 1,
21     FAILED: 2
22   };
24   /**
25    * The preference is a boolean that enables/disables spell checking.
26    * @type {string}
27    * @const
28    */
29   var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking';
31   /**
32    * The preference is a CSV string that describes preload engines
33    * (i.e. active input methods).
34    * @type {string}
35    * @const
36    */
37   var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines';
39   /**
40    * The preference that lists the extension IMEs that are enabled in the
41    * language menu.
42    * @type {string}
43    * @const
44    */
45   var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes';
47   /**
48    * The preference that lists the languages which are not translated.
49    * @type {string}
50    * @const
51    */
52   var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages';
54   /**
55    * The preference key that is a list of strings that describes the spellcheck
56    * dictionary language, like ["en-US", "fr"].
57    * @type {string}
58    * @const
59    */
60   var SPELL_CHECK_DICTIONARIES_PREF = 'spellcheck.dictionaries';
62   /**
63    * The preference that indicates if the Translate feature is enabled.
64    * @type {string}
65    * @const
66    */
67   var ENABLE_TRANSLATE = 'translate.enabled';
69   /////////////////////////////////////////////////////////////////////////////
70   // LanguageOptions class:
72   /**
73    * Encapsulated handling of ChromeOS language options page.
74    * @constructor
75    * @extends {cr.ui.pageManager.Page}
76    */
77   function LanguageOptions(model) {
78     Page.call(this, 'languages',
79               loadTimeData.getString('languagePageTabTitle'), 'languagePage');
80   }
82   cr.addSingletonGetter(LanguageOptions);
84   // Inherit LanguageOptions from Page.
85   LanguageOptions.prototype = {
86     __proto__: Page.prototype,
88     /**
89      * For recording the prospective language (the next locale after relaunch).
90      * @type {?string}
91      * @private
92      */
93     prospectiveUiLanguageCode_: null,
95     /**
96      * Map from language code to spell check dictionary download status for that
97      * language.
98      * @type {Array}
99      * @private
100      */
101     spellcheckDictionaryDownloadStatus_: [],
103     /**
104      * Number of times a spell check dictionary download failed.
105      * @type {number}
106      * @private
107      */
108     spellcheckDictionaryDownloadFailures_: 0,
110     /**
111      * The list of preload engines, like ['mozc', 'pinyin'].
112      * @type {Array}
113      * @private
114      */
115     preloadEngines_: [],
117     /**
118      * The list of extension IMEs that are enabled out of the language menu.
119      * @type {Array}
120      * @private
121      */
122     enabledExtensionImes_: [],
124     /**
125      * The list of the languages which is not translated.
126      * @type {Array}
127      * @private
128      */
129     translateBlockedLanguages_: [],
131     /**
132      * The list of the languages supported by Translate server
133      * @type {Array}
134      * @private
135      */
136     translateSupportedLanguages_: [],
138     /**
139      * The dictionary of currently selected spellcheck dictionary languages,
140      * like {"en-US": true, "sl-SI": true}.
141      * @type {!Object}
142      * @private
143      */
144     spellCheckLanguages_: {},
146     /**
147      * The map of language code to input method IDs, like:
148      * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
149      * @type {Object}
150      * @private
151      */
152     languageCodeToInputMethodIdsMap_: {},
154     /**
155      * The value that indicates if Translate feature is enabled or not.
156      * @type {boolean}
157      * @private
158      */
159     enableTranslate_: false,
161     /**
162      * Returns true if the enable-multilingual-spellchecker flag is set.
163      * @return {boolean}
164      * @private
165      */
166     isMultilingualSpellcheckerEnabled_: function() {
167       return loadTimeData.getBoolean('enableMultilingualSpellChecker');
168     },
170     /** @override */
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));
187       if (cr.isChromeOS) {
188         this.initializeInputMethodList_();
189         this.initializeLanguageCodeToInputMethodIdsMap_();
190       }
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-]+)/);
211         if (match) {
212           var addLanguageCode = match[1];
213           $('language-options-list').addLanguage(addLanguageCode);
214           this.addBlockedLanguage_(addLanguageCode);
215         } else {
216           PageManager.showPageByName('addLanguage');
217           chrome.send('coreOptionsUserMetricsAction',
218                       ['Options_Languages_Add']);
219         }
220       };
221       $('language-options-add-button').onclick = onclick.bind(this);
223       if (!cr.isMac) {
224         // Set up the button for editing custom spelling dictionary.
225         $('edit-custom-dictionary-button').onclick = function(e) {
226           PageManager.showPageByName('editDictionary');
227         };
228         $('dictionary-download-retry-button').onclick = function(e) {
229           chrome.send('retryDictionaryDownload');
230         };
231       }
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;
241       }
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));
248         }
249         $('enable-spellcheck-container').hidden =
250             this.isMultilingualSpellcheckerEnabled_();
251       }
253       // Handle clicks on "Use this language for spell checking" button.
254       if (!cr.isMac) {
255         if (this.isMultilingualSpellcheckerEnabled_()) {
256           $('spellcheck-language-checkbox').addEventListener(
257               'change',
258               this.handleSpellCheckLanguageCheckboxClick_.bind(this));
259         } else {
260           $('spellcheck-language-button').addEventListener(
261               'click',
262               this.handleSpellCheckLanguageButtonClick_.bind(this));
263         }
264       }
266       if (cr.isChromeOS) {
267         $('language-options-ui-restart-button').onclick = function() {
268           chrome.send('uiLanguageRestart');
269         };
270       }
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);
279     },
281     /**
282      * Initializes the input method list.
283      */
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));
304     },
306     /**
307      * Appends input method lists based on component extension ime list.
308      * @param {!Array} componentExtensionImeList A list of input method
309      *     descriptors.
310      * @private
311      */
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(
320                 inputMethod.id);
321           } else {
322             this.languageCodeToInputMethodIdsMap_[languageCode] =
323                 [inputMethod.id];
324           }
325         }
326       }
327     },
329     /**
330      * Appends input methods into input method list.
331      * @param {!Array} inputMethods A list of input method descriptors.
332      * @private
333      */
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);
341         element.id = '';
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);
358         }
360         // Listen to user clicks.
361         input.addEventListener('click',
362                                this.handleCheckboxClick_.bind(this));
363         inputMethodList.appendChild(element);
364       }
365     },
367     /**
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'
372      * @private
373      */
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);
380       }
381     },
383     /**
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'
388      * @private
389      */
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;
397                 });
398         Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
399                                 this.translateBlockedLanguages_, true);
400       }
401     },
403     /**
404      * Handles Page's visible property change event.
405      * @param {Event} e Property change event.
406      * @private
407      */
408     handleVisibleChange_: function(e) {
409       if (this.visible) {
410         $('language-options-list').redraw();
411         chrome.send('languageOptionsOpen');
412       }
413     },
415     /**
416      * Handles languageOptionsList's change event.
417      * @param {Event} e Change event.
418      * @private
419      */
420     handleLanguageOptionsListChange_: function(e) {
421       var languageOptionsList = $('language-options-list');
422       var languageCode = languageOptionsList.getSelectedLanguageCode();
424       // If there's no selection, just return.
425       if (!languageCode)
426         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-]+)/);
431       if (match) {
432         var specifiedLanguageCode = match[1];
433         if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
434           languageCode = specifiedLanguageCode;
435         }
436       }
438       this.updateOfferToTranslateCheckbox_(languageCode);
440       if (cr.isWindows || cr.isChromeOS)
441         this.updateUiLanguageButton_(languageCode);
443       this.updateSelectedLanguageName_(languageCode);
445       if (!cr.isMac)
446         this.updateSpellCheckLanguageControls_(languageCode);
448       if (cr.isChromeOS)
449         this.updateInputMethodList_(languageCode);
451       this.updateLanguageListInAddLanguageOverlay_();
452     },
454     /**
455      * Handles languageOptionsList's save event.
456      * @param {Event} e Save event.
457      * @private
458      */
459     handleLanguageOptionsListSave_: function(e) {
460       if (cr.isChromeOS) {
461         // Sort the preload engines per the saved languages before save.
462         this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
463         this.savePreloadEnginesPref_();
464       }
465     },
467     /**
468      * Sorts preloadEngines_ by languageOptionsList's order.
469      * @param {Array} preloadEngines List of preload engines.
470      * @return {Array} Returns sorted preloadEngines.
471      * @private
472      */
473     sortPreloadEngines_: function(preloadEngines) {
474       // For instance, suppose we have two languages and associated input
475       // methods:
476       //
477       // - Korean: hangul
478       // - Chinese: pinyin
479       //
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;
490       }
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_[
497             languageCode];
498         if (!inputMethodIds)
499           continue;
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];
511           }
512         }
513       }
515       return newPreloadEngines;
516     },
518     /**
519      * Initializes the map of language code to input method IDs.
520      * @private
521      */
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(
529                 inputMethod.id);
530           } else {
531             this.languageCodeToInputMethodIdsMap_[languageCode] =
532                 [inputMethod.id];
533           }
534         }
535       }
536     },
538     /**
539      * Updates the currently selected language name.
540      * @param {string} languageCode Language code (ex. "fr").
541      * @private
542      */
543     updateSelectedLanguageName_: function(languageCode) {
544       var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
545           languageCode);
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;
553       }
555       // Update the currently selected language name.
556       var languageName = $('language-options-language-name');
557       languageName.textContent = languageDisplayName;
558       languageName.dir = textDirection;
559     },
561     /**
562      * Updates the UI language button.
563      * @param {string} languageCode Language code (ex. "fr").
564      * @private
565      */
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;
594         } else {
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');
602           } else {
603             uiLanguageButton.onclick = function(e) {
604               chrome.send('uiLanguageChange', [languageCode]);
605             };
606           }
607           showMutuallyExclusiveNodes(
608               [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
609         }
610       } else {
611         uiLanguageMessage.textContent =
612             loadTimeData.getString('cannotBeDisplayedInThisLanguage');
613         showMutuallyExclusiveNodes(
614             [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
615       }
616     },
618     /**
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").
622      * @private
623      */
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;
655         return;
656       }
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;
671         }
672       } else {
673         spellCheckLanguageButton.textContent =
674             loadTimeData.getString('useThisForSpellChecking');
675         spellCheckLanguageButton.hidden = false;
676         spellCheckLanguageButton.languageCode = languageCode;
677       }
679       switch (this.spellcheckDictionaryDownloadStatus_[languageCode]) {
680         case DOWNLOAD_STATUS.IN_PROGRESS:
681           dictionaryDownloadInProgress.hidden = false;
682           break;
683         case DOWNLOAD_STATUS.FAILED:
684           showMutuallyExclusiveNodes(
685               [spellCheckLanguageSection, dictionaryDownloadFailed], 1);
686           if (this.spellcheckDictionaryDownloadFailures_ > 1)
687             dictionaryDownloadFailHelp.hidden = false;
688           break;
689       }
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;
699     },
701     /**
702      * Updates the checkbox for stopping translation.
703      * @param {string} languageCode Language code (ex. "fr").
704      * @private
705      */
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') {
713         div.hidden = false;
714       } else {
715         div.hidden = true;
716         return;
717       }
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);
726       } else {
727         showMutuallyExclusiveNodes(nodes, 1);
728         return;
729       }
731       var checkbox = $('offer-to-translate-in-this-language');
733       if (!this.enableTranslate_) {
734         checkbox.disabled = true;
735         checkbox.checked = false;
736         return;
737       }
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;
747         return;
748       }
750       checkbox.disabled = false;
752       var blockedLanguages = this.translateBlockedLanguages_;
753       var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
754       checkbox.checked = checked;
755     },
757     /**
758      * Updates the input method list.
759      * @param {string} languageCode Language code (ex. "fr").
760      * @private
761      */
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/);
767       if (match) {
768         focusInputMethodId = match[1];
769       }
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) {
781             input.focus();
782           }
783         } else {
784           method.hidden = true;
785         }
786       }
788       $('language-options-input-method-none').hidden =
789           (languageCode in this.languageCodeToInputMethodIdsMap_);
791       if (focusInputMethodId == 'add') {
792         $('language-options-add-button').focus();
793       }
794     },
796     /**
797      * Updates the language list in the add language overlay.
798      * @private
799      */
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;
809       }
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;
823         }
824       }
825     },
827     /**
828      * Handles preloadEnginesPref change.
829      * @param {Event} e Change event.
830      * @private
831      */
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();
837     },
839     /**
840      * Handles enabledExtensionImePref change.
841      * @param {Event} e Change event.
842      * @private
843      */
844     handleEnabledExtensionsPrefChange_: function(e) {
845       var value = e.value.value;
846       this.enabledExtensionImes_ = value.split(',');
847       this.updateCheckboxesFromEnabledExtensions_();
848     },
850     /**
851      * Handles offer-to-translate checkbox's click event.
852      * @param {Event} e Click event.
853      * @private
854      */
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();
862       if (checked)
863         this.removeBlockedLanguage_(selectedLanguageCode);
864       else
865         this.addBlockedLanguage_(selectedLanguageCode);
866     },
868     /**
869      * Handles input method checkbox's click event.
870      * @param {Event} e Click event.
871      * @private
872      */
873     handleCheckboxClick_: function(e) {
874       var checkbox = assertInstanceof(e.target, Element);
876       // Third party IMEs require additional confirmation prior to enabling due
877       // to privacy risk.
878       if (/^_ext_ime_/.test(checkbox.inputMethodId) && checkbox.checked) {
879         var confirmationCallback = this.handleCheckboxUpdate_.bind(this,
880                                                                    checkbox);
881         var cancellationCallback = function() {
882           checkbox.checked = false;
883         };
884         ThirdPartyImeConfirmOverlay.showConfirmationDialog({
885           extension: checkbox.imeProvider,
886           confirm: confirmationCallback,
887           cancel: cancellationCallback
888         });
889       } else {
890         this.handleCheckboxUpdate_(checkbox);
891       }
893       chrome.send('coreOptionsUserMetricsAction',
894                   ['Options_Languages_InputMethodCheckbox' +
895                    (checkbox.checked ? '_Enable' : '_Disable')]);
896     },
898     /**
899      * Updates active IMEs based on change in state of a checkbox for an input
900      * method.
901      * @param {!Element} checkbox Updated checkbox element.
902      * @private
903      */
904     handleCheckboxUpdate_: function(checkbox) {
905       if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
906         this.updateEnabledExtensionsFromCheckboxes_();
907         this.saveEnabledExtensionPref_();
908         return;
909       }
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;
916         return;
917       }
918       if (checkbox.checked) {
919         chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
920       } else {
921         chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
922       }
923       this.updatePreloadEnginesFromCheckboxes_();
924       this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
925       this.savePreloadEnginesPref_();
926     },
928     /**
929      * Handles clicks on the "OK" button of the "Add language" dialog.
930      * @param {Event} e Click event.
931      * @private
932      */
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();
942       }
943     },
945     /**
946      * Checks if languageCode is deletable or not.
947      * @param {string} languageCode the languageCode to check for deletability.
948      */
949     languageIsDeletable: function(languageCode) {
950       // Don't allow removing the language if it's a UI language.
951       if (languageCode == this.prospectiveUiLanguageCode_)
952         return false;
953       return (!cr.isChromeOS ||
954               this.canDeleteLanguage_(languageCode));
955     },
957     /**
958      * Handles browse.enable_spellchecking change.
959      * @param {Event} e Change event.
960      * @private
961      */
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;
967       if (!cr.isMac)
968         $('edit-custom-dictionary-button').hidden = value;
969     },
971     /**
972      * Handles translateBlockedLanguagesPref change.
973      * @param {Event} e Change event.
974      * @private
975      */
976     handleTranslateBlockedLanguagesPrefChange_: function(e) {
977       this.translateBlockedLanguages_ = e.value.value;
978       this.updateOfferToTranslateCheckbox_(
979           $('language-options-list').getSelectedLanguageCode());
980     },
982     /**
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.
987      * @private
988      */
989     handleSpellCheckDictionariesPrefChange_: function(e) {
990       if (cr.isMac)
991         return;
993       var languages = e.value.value;
994       this.spellCheckLanguages_ = {};
995       for (var i = 0; i < languages.length; i++) {
996         this.spellCheckLanguages_[languages[i]] = true;
997       }
998       this.updateSpellCheckLanguageControls_(
999           $('language-options-list').getSelectedLanguageCode());
1000     },
1002     /**
1003      * Handles translate.enabled change.
1004      * @param {Event} e Change event.
1005      * @private
1006      */
1007     handleEnableTranslatePrefChange_: function(e) {
1008       var enabled = e.value.value;
1009       this.enableTranslate_ = enabled;
1010       this.updateOfferToTranslateCheckbox_(
1011           $('language-options-list').getSelectedLanguageCode());
1012     },
1014     /**
1015      * Handles spellCheckLanguageButton click.
1016      * @param {Event} e Click event.
1017      * @private
1018      */
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']);
1029     },
1031     /**
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.
1036      * @private
1037      */
1038     handleSpellCheckLanguageCheckboxClick_: function(e) {
1039       var languageCode = e.currentTarget.languageCode;
1041       if (e.currentTarget.checked)
1042         this.spellCheckLanguages_[languageCode] = true;
1043       else
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']);
1054     },
1056     /**
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.
1060      *
1061      * @param {string} languageCode Language code (ex. "fr").
1062      * @return {boolean} Returns true on success.
1063      * @private
1064      */
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)
1073         return true;
1075       for (var i = 0; i < inputMethodIds.length; i++) {
1076         enginesToBeRemovedSet[inputMethodIds[i]] = true;
1077       }
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) {
1085           continue;
1086         }
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)
1093           continue;
1095         for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
1096           var inputMethodId = inputMethodIdsForAnotherLanguage[j];
1097           if (inputMethodId in enginesToBeRemovedSet) {
1098             delete enginesToBeRemovedSet[inputMethodId];
1099           }
1100         }
1101       }
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]);
1108         }
1109       }
1110       // Don't allow this operation if it causes the number of preload
1111       // engines to be zero.
1112       return (newPreloadEngines.length > 0);
1113     },
1115     /**
1116      * Saves the enabled extension preference.
1117      * @private
1118      */
1119     saveEnabledExtensionPref_: function() {
1120       Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
1121                                 this.enabledExtensionImes_.join(','), true);
1122     },
1124     /**
1125      * Updates the checkboxes in the input method list from the enabled
1126      * extensions preference.
1127      * @private
1128      */
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);
1140       }
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);
1146         }
1147       }
1148     },
1150     /**
1151      * Updates the enabled extensions preference from the checkboxes in the
1152      * input method list.
1153      * @private
1154      */
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);
1163         }
1164       }
1165     },
1167     /**
1168      * Saves the preload engines preference.
1169      * @private
1170      */
1171     savePreloadEnginesPref_: function() {
1172       Preferences.setStringPref(PRELOAD_ENGINES_PREF,
1173                                 this.preloadEngines_.join(','), true);
1174     },
1176     /**
1177      * Updates the checkboxes in the input method list from the preload
1178      * engines preference.
1179      * @private
1180      */
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;
1186       }
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);
1193       }
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);
1199         }
1200       }
1201     },
1203     /**
1204      * Updates the preload engines preference from the checkboxes in the
1205      * input method list.
1206      * @private
1207      */
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);
1216         }
1217       }
1218       var languageOptionsList = $('language-options-list');
1219       languageOptionsList.updateDeletable();
1220     },
1222     /**
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.
1226      * @private
1227      */
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;
1234       }
1236       var enabledPreloadEngines = [];
1237       var seen = {};
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;
1247         }
1248       }
1249       return enabledPreloadEngines;
1250     },
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.
1255     /**
1256      * @private
1257      */
1258     notificationTimeout_: null,
1260     /**
1261      * Shows notification.
1262      * @param {string} text
1263      * @param {string} actionText
1264      * @param {number=} opt_delay
1265      * @private
1266      */
1267     showNotification_: function(text, actionText, opt_delay) {
1268       var notificationElement = $('notification');
1269       var actionLink = notificationElement.querySelector('.link-color');
1270       var delay = opt_delay || 10000;
1272       function show() {
1273         window.clearTimeout(this.notificationTimeout_);
1274         notificationElement.classList.add('show');
1275         document.body.classList.add('notification-shown');
1276       }
1278       function hide() {
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.
1289         actionLink.blur();
1290       }
1292       function delayedHide() {
1293         this.notificationTimeout_ = window.setTimeout(hide, delay);
1294       }
1296       notificationElement.firstElementChild.textContent = text;
1297       actionLink.textContent = actionText;
1299       actionLink.onclick = hide;
1300       actionLink.onkeydown = function(e) {
1301         if (e.keyIdentifier == 'Enter') {
1302           hide();
1303         }
1304       };
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;
1312       show();
1313       delayedHide();
1314     },
1316     /**
1317      * Chrome callback for when the UI language preference is saved.
1318      * @param {string} languageCode The newly selected language to use.
1319      * @private
1320      */
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)
1326         return;
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);
1333         return;
1334       }
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')],
1340                                  1);
1341     },
1343     /**
1344      * A handler for when dictionary for |languageCode| begins downloading.
1345      * @param {string} languageCode The language of the dictionary that just
1346      *     began downloading.
1347      * @private
1348      */
1349     onDictionaryDownloadBegin_: function(languageCode) {
1350       this.spellcheckDictionaryDownloadStatus_[languageCode] =
1351           DOWNLOAD_STATUS.IN_PROGRESS;
1352       if (!cr.isMac &&
1353           languageCode ==
1354               $('language-options-list').getSelectedLanguageCode()) {
1355         this.updateSpellCheckLanguageControls_(languageCode);
1356       }
1357     },
1359     /**
1360      * A handler for when dictionary for |languageCode| successfully downloaded.
1361      * @param {string} languageCode The language of the dictionary that
1362      *     succeeded downloading.
1363      * @private
1364      */
1365     onDictionaryDownloadSuccess_: function(languageCode) {
1366       delete this.spellcheckDictionaryDownloadStatus_[languageCode];
1367       this.spellcheckDictionaryDownloadFailures_ = 0;
1368       if (!cr.isMac &&
1369           languageCode ==
1370               $('language-options-list').getSelectedLanguageCode()) {
1371         this.updateSpellCheckLanguageControls_(languageCode);
1372       }
1373     },
1375     /**
1376      * A handler for when dictionary for |languageCode| fails to download.
1377      * @param {string} languageCode The language of the dictionary that failed
1378      *     to download.
1379      * @private
1380      */
1381     onDictionaryDownloadFailure_: function(languageCode) {
1382       this.spellcheckDictionaryDownloadStatus_[languageCode] =
1383           DOWNLOAD_STATUS.FAILED;
1384       this.spellcheckDictionaryDownloadFailures_++;
1385       if (!cr.isMac &&
1386           languageCode ==
1387               $('language-options-list').getSelectedLanguageCode()) {
1388         this.updateSpellCheckLanguageControls_(languageCode);
1389       }
1390     },
1392     /**
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.
1397      * @private
1398      */
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
1404       var synonyms = {
1405         'nb': 'no',
1406         'he': 'iw',
1407         'jv': 'jw',
1408         'fil': 'tl',
1409         'zh-HK': 'zh-TW',
1410         'zh-MO': 'zh-TW',
1411         'zh-SG': 'zh-CN',
1412       };
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;
1420       }
1422       return main;
1423     },
1424   };
1426   /**
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.
1430    */
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;
1436     }
1437   }
1439   LanguageOptions.uiLanguageSaved = function(languageCode) {
1440     LanguageOptions.getInstance().uiLanguageSaved_(languageCode);
1441   };
1443   LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
1444     LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
1445   };
1447   LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
1448     LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
1449   };
1451   LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
1452     LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
1453   };
1455   // Export
1456   return {
1457     LanguageOptions: LanguageOptions
1458   };