Home | History | Annotate | Download | only in options
      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.
      4 
      5 // TODO(kochi): Generalize the notification as a component and put it
      6 // in js/cr/ui/notification.js .
      7 
      8 cr.define('options', function() {
      9   /** @const */ var OptionsPage = options.OptionsPage;
     10   /** @const */ var LanguageList = options.LanguageList;
     11 
     12   // Some input methods like Chinese Pinyin have config pages.
     13   // This is the map of the input method names to their config page names.
     14   /** @const */ var INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME = {
     15     'mozc': 'languageMozc',
     16     'mozc-chewing': 'languageChewing',
     17     'mozc-dv': 'languageMozc',
     18     'mozc-hangul': 'languageHangul',
     19     'mozc-jp': 'languageMozc',
     20     'pinyin': 'languagePinyin',
     21     'pinyin-dv': 'languagePinyin',
     22   };
     23 
     24   /**
     25    * Spell check dictionary download status.
     26    * @type {Enum}
     27    */
     28   /** @const*/ var DOWNLOAD_STATUS = {
     29     IN_PROGRESS: 1,
     30     FAILED: 2
     31   };
     32 
     33   /**
     34    * The preference is a boolean that enables/disables spell checking.
     35    * @type {string}
     36    * @const
     37    */
     38   var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking';
     39 
     40   /**
     41    * The preference is a CSV string that describes preload engines
     42    * (i.e. active input methods).
     43    * @type {string}
     44    * @const
     45    */
     46   var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines';
     47 
     48   /**
     49    * The preference that lists the extension IMEs that are enabled in the
     50    * language menu.
     51    * @type {string}
     52    * @const
     53    */
     54   var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes';
     55 
     56   /**
     57    * The preference that lists the languages which are not translated.
     58    * @type {string}
     59    * @const
     60    */
     61   var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages';
     62 
     63   /**
     64    * The preference key that is a string that describes the spell check
     65    * dictionary language, like "en-US".
     66    * @type {string}
     67    * @const
     68    */
     69   var SPELL_CHECK_DICTIONARY_PREF = 'spellcheck.dictionary';
     70 
     71   /////////////////////////////////////////////////////////////////////////////
     72   // LanguageOptions class:
     73 
     74   /**
     75    * Encapsulated handling of ChromeOS language options page.
     76    * @constructor
     77    */
     78   function LanguageOptions(model) {
     79     OptionsPage.call(this, 'languages',
     80                      loadTimeData.getString('languagePageTabTitle'),
     81                      'languagePage');
     82   }
     83 
     84   cr.addSingletonGetter(LanguageOptions);
     85 
     86   // Inherit LanguageOptions from OptionsPage.
     87   LanguageOptions.prototype = {
     88     __proto__: OptionsPage.prototype,
     89 
     90     /* For recording the prospective language (the next locale after relaunch).
     91      * @type {?string}
     92      * @private
     93      */
     94     prospectiveUiLanguageCode_: null,
     95 
     96     /*
     97      * Map from language code to spell check dictionary download status for that
     98      * language.
     99      * @type {Array}
    100      * @private
    101      */
    102     spellcheckDictionaryDownloadStatus_: [],
    103 
    104     /**
    105      * Number of times a spell check dictionary download failed.
    106      * @type {int}
    107      * @private
    108      */
    109     spellcheckDictionaryDownloadFailures_: 0,
    110 
    111     /**
    112      * The list of preload engines, like ['mozc', 'pinyin'].
    113      * @type {Array}
    114      * @private
    115      */
    116     preloadEngines_: [],
    117 
    118     /**
    119      * The list of extension IMEs that are enabled out of the language menu.
    120      * @type {Array}
    121      * @private
    122      */
    123     enabledExtensionImes_: [],
    124 
    125     /**
    126      * The list of the languages which is not translated.
    127      * @type {Array}
    128      * @private
    129      */
    130     translateBlockedLanguages_: [],
    131 
    132     /**
    133      * The list of the languages supported by Translate server
    134      * @type {Array}
    135      * @private
    136      */
    137     translateSupportedLanguages_: [],
    138 
    139     /**
    140      * The preference is a string that describes the spell check dictionary
    141      * language, like "en-US".
    142      * @type {string}
    143      * @private
    144      */
    145     spellCheckDictionary_: '',
    146 
    147     /**
    148      * The map of language code to input method IDs, like:
    149      * {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
    150      * @type {Object}
    151      * @private
    152      */
    153     languageCodeToInputMethodIdsMap_: {},
    154 
    155     /**
    156      * Initializes LanguageOptions page.
    157      * Calls base class implementation to start preference initialization.
    158      */
    159     initializePage: function() {
    160       OptionsPage.prototype.initializePage.call(this);
    161 
    162       var languageOptionsList = $('language-options-list');
    163       LanguageList.decorate(languageOptionsList);
    164 
    165       languageOptionsList.addEventListener('change',
    166           this.handleLanguageOptionsListChange_.bind(this));
    167       languageOptionsList.addEventListener('save',
    168           this.handleLanguageOptionsListSave_.bind(this));
    169 
    170       this.prospectiveUiLanguageCode_ =
    171           loadTimeData.getString('prospectiveUiLanguageCode');
    172       this.addEventListener('visibleChange',
    173                             this.handleVisibleChange_.bind(this));
    174 
    175       if (cr.isChromeOS) {
    176         $('chewing-confirm').onclick = $('hangul-confirm').onclick =
    177             $('mozc-confirm').onclick = $('pinyin-confirm').onclick =
    178                 OptionsPage.closeOverlay.bind(OptionsPage);
    179 
    180         this.initializeInputMethodList_();
    181         this.initializeLanguageCodeToInputMethodIdsMap_();
    182       }
    183 
    184       var checkbox = $('dont-translate-in-this-language');
    185       checkbox.addEventListener('click',
    186           this.handleDontTranslateCheckboxClick_.bind(this));
    187 
    188       Preferences.getInstance().addEventListener(
    189           TRANSLATE_BLOCKED_LANGUAGES_PREF,
    190           this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
    191       Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARY_PREF,
    192           this.handleSpellCheckDictionaryPrefChange_.bind(this));
    193       this.translateSupportedLanguages_ =
    194           loadTimeData.getValue('translateSupportedLanguages');
    195 
    196       // Set up add button.
    197       $('language-options-add-button').onclick = function(e) {
    198         // Add the language without showing the overlay if it's specified in
    199         // the URL hash (ex. lang_add=ja).  Used for automated testing.
    200         var match = document.location.hash.match(/\blang_add=([\w-]+)/);
    201         if (match) {
    202           var addLanguageCode = match[1];
    203           $('language-options-list').addLanguage(addLanguageCode);
    204           this.addBlockedLanguage_(addLanguageCode);
    205         } else {
    206           OptionsPage.navigateToPage('addLanguage');
    207         }
    208       }.bind(this);
    209 
    210       if (!cr.isMac) {
    211         // Set up the button for editing custom spelling dictionary.
    212         $('edit-dictionary-button').onclick = function(e) {
    213           OptionsPage.navigateToPage('editDictionary');
    214         };
    215         $('dictionary-download-retry-button').onclick = function(e) {
    216           chrome.send('retryDictionaryDownload');
    217         };
    218       }
    219 
    220       // Listen to add language dialog ok button.
    221       $('add-language-overlay-ok-button').addEventListener(
    222           'click', this.handleAddLanguageOkButtonClick_.bind(this));
    223 
    224       if (!cr.isChromeOS) {
    225         // Show experimental features if enabled.
    226         if (loadTimeData.getBoolean('enableSpellingAutoCorrect'))
    227           $('auto-spell-correction-option').hidden = false;
    228 
    229         // Handle spell check enable/disable.
    230         if (!cr.isMac) {
    231           Preferences.getInstance().addEventListener(
    232               ENABLE_SPELL_CHECK_PREF,
    233               this.updateEnableSpellCheck_.bind(this));
    234         }
    235       }
    236 
    237       // Handle clicks on "Use this language for spell checking" button.
    238       if (!cr.isMac) {
    239         var spellCheckLanguageButton = getRequiredElement(
    240             'language-options-spell-check-language-button');
    241         spellCheckLanguageButton.addEventListener(
    242             'click',
    243             this.handleSpellCheckLanguageButtonClick_.bind(this));
    244       }
    245 
    246       if (cr.isChromeOS) {
    247         $('language-options-ui-restart-button').onclick = function() {
    248           chrome.send('uiLanguageRestart');
    249         };
    250       }
    251 
    252       $('language-confirm').onclick =
    253           OptionsPage.closeOverlay.bind(OptionsPage);
    254     },
    255 
    256     /**
    257      * Initializes the input method list.
    258      */
    259     initializeInputMethodList_: function() {
    260       var inputMethodList = $('language-options-input-method-list');
    261       var inputMethodPrototype = $('language-options-input-method-template');
    262 
    263       // Add all input methods, but make all of them invisible here. We'll
    264       // change the visibility in handleLanguageOptionsListChange_() based
    265       // on the selected language. Note that we only have less than 100
    266       // input methods, so creating DOM nodes at once here should be ok.
    267       this.appendInputMethodElement_(loadTimeData.getValue('inputMethodList'));
    268       this.appendInputMethodElement_(loadTimeData.getValue('extensionImeList'));
    269       this.appendComponentExtensionIme_(
    270           loadTimeData.getValue('componentExtensionImeList'));
    271 
    272       // Listen to pref change once the input method list is initialized.
    273       Preferences.getInstance().addEventListener(
    274           PRELOAD_ENGINES_PREF,
    275           this.handlePreloadEnginesPrefChange_.bind(this));
    276       Preferences.getInstance().addEventListener(
    277           ENABLED_EXTENSION_IME_PREF,
    278           this.handleEnabledExtensionsPrefChange_.bind(this));
    279     },
    280 
    281     /**
    282      * Appends input method lists based on component extension ime list.
    283      * @param {!Array} componentExtensionImeList A list of input method
    284      *     descriptors.
    285      * @private
    286      */
    287     appendComponentExtensionIme_: function(componentExtensionImeList) {
    288       this.appendInputMethodElement_(componentExtensionImeList);
    289 
    290       for (var i = 0; i < componentExtensionImeList.length; i++) {
    291         var inputMethod = componentExtensionImeList[i];
    292         for (var languageCode in inputMethod.languageCodeSet) {
    293           if (languageCode in this.languageCodeToInputMethodIdsMap_) {
    294             this.languageCodeToInputMethodIdsMap_[languageCode].push(
    295                 inputMethod.id);
    296           } else {
    297             this.languageCodeToInputMethodIdsMap_[languageCode] =
    298                 [inputMethod.id];
    299           }
    300         }
    301       }
    302     },
    303 
    304     /**
    305      * Appends input methods into input method list.
    306      * @param {!Array} inputMethods A list of input method descriptors.
    307      * @private
    308      */
    309     appendInputMethodElement_: function(inputMethods) {
    310       var inputMethodList = $('language-options-input-method-list');
    311       var inputMethodTemplate = $('language-options-input-method-template');
    312 
    313       for (var i = 0; i < inputMethods.length; i++) {
    314         var inputMethod = inputMethods[i];
    315         var element = inputMethodTemplate.cloneNode(true);
    316         element.id = '';
    317         element.languageCodeSet = inputMethod.languageCodeSet;
    318 
    319         var input = element.querySelector('input');
    320         input.inputMethodId = inputMethod.id;
    321         var span = element.querySelector('span');
    322         span.textContent = inputMethod.displayName;
    323 
    324         // Add the configure button if the config page is present for this
    325         // input method.
    326         if (inputMethod.id in INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME) {
    327           var pageName = INPUT_METHOD_ID_TO_CONFIG_PAGE_NAME[inputMethod.id];
    328           var button = this.createConfigureInputMethodButton_(inputMethod.id,
    329                                                               pageName);
    330           element.appendChild(button);
    331         }
    332 
    333         if (inputMethod.optionsPage) {
    334           var button = document.createElement('button');
    335           button.textContent = loadTimeData.getString('configure');
    336           button.inputMethodId = inputMethod.id;
    337           button.onclick = function(optionsPage, e) {
    338             window.open(optionsPage);
    339           }.bind(this, inputMethod.optionsPage);
    340           element.appendChild(button);
    341         }
    342 
    343         // Listen to user clicks.
    344         input.addEventListener('click',
    345                                this.handleCheckboxClick_.bind(this));
    346         inputMethodList.appendChild(element);
    347       }
    348     },
    349 
    350     /**
    351      * Creates a configure button for the given input method ID.
    352      * @param {string} inputMethodId Input method ID (ex. "pinyin").
    353      * @param {string} pageName Name of the config page (ex. "languagePinyin").
    354      * @private
    355      */
    356     createConfigureInputMethodButton_: function(inputMethodId, pageName) {
    357       var button = document.createElement('button');
    358       button.textContent = loadTimeData.getString('configure');
    359       button.onclick = function(e) {
    360         // Prevent the default action (i.e. changing the checked property
    361         // of the checkbox). The button click here should not be handled
    362         // as checkbox click.
    363         e.preventDefault();
    364         chrome.send('inputMethodOptionsOpen', [inputMethodId]);
    365         OptionsPage.navigateToPage(pageName);
    366       };
    367       return button;
    368     },
    369 
    370     /**
    371      * Adds a language to the preference 'translate_blocked_languages'. If
    372      * |langCode| is already added, nothing happens. |langCode| is converted
    373      * to a Translate language synonym before added.
    374      * @param {string} langCode A language code like 'en'
    375      * @private
    376      */
    377     addBlockedLanguage_: function(langCode) {
    378       langCode = this.convertLangCodeForTranslation_(langCode);
    379       if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
    380         this.translateBlockedLanguages_.push(langCode);
    381         Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
    382                                 this.translateBlockedLanguages_, true);
    383       }
    384     },
    385 
    386     /**
    387      * Removes a language from the preference 'translate_blocked_languages'.
    388      * If |langCode| doesn't exist in the preference, nothing happens.
    389      * |langCode| is converted to a Translate language synonym before removed.
    390      * @param {string} langCode A language code like 'en'
    391      * @private
    392      */
    393     removeBlockedLanguage_: function(langCode) {
    394       langCode = this.convertLangCodeForTranslation_(langCode);
    395       if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
    396         this.translateBlockedLanguages_ =
    397             this.translateBlockedLanguages_.filter(
    398                 function(langCodeNotTranslated) {
    399                   return langCodeNotTranslated != langCode;
    400                 });
    401         Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
    402                                 this.translateBlockedLanguages_, true);
    403       }
    404     },
    405 
    406     /**
    407      * Handles OptionsPage's visible property change event.
    408      * @param {Event} e Property change event.
    409      * @private
    410      */
    411     handleVisibleChange_: function(e) {
    412       if (this.visible) {
    413         $('language-options-list').redraw();
    414         chrome.send('languageOptionsOpen');
    415       }
    416     },
    417 
    418     /**
    419      * Handles languageOptionsList's change event.
    420      * @param {Event} e Change event.
    421      * @private
    422      */
    423     handleLanguageOptionsListChange_: function(e) {
    424       var languageOptionsList = $('language-options-list');
    425       var languageCode = languageOptionsList.getSelectedLanguageCode();
    426 
    427       // If there's no selection, just return.
    428       if (!languageCode)
    429         return;
    430 
    431       // Select the language if it's specified in the URL hash (ex. lang=ja).
    432       // Used for automated testing.
    433       var match = document.location.hash.match(/\blang=([\w-]+)/);
    434       if (match) {
    435         var specifiedLanguageCode = match[1];
    436         if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
    437           languageCode = specifiedLanguageCode;
    438         }
    439       }
    440 
    441       this.updateDontTranslateCheckbox_(languageCode);
    442 
    443       if (cr.isWindows || cr.isChromeOS)
    444         this.updateUiLanguageButton_(languageCode);
    445 
    446       if (!cr.isMac) {
    447         this.updateSelectedLanguageName_(languageCode);
    448         this.updateSpellCheckLanguageButton_(languageCode);
    449       }
    450 
    451       if (cr.isChromeOS)
    452         this.updateInputMethodList_(languageCode);
    453 
    454       this.updateLanguageListInAddLanguageOverlay_();
    455     },
    456 
    457     /**
    458      * Happens when a user changes back to the language they're currently using.
    459      */
    460     currentLocaleWasReselected: function() {
    461       this.updateUiLanguageButton_(
    462           loadTimeData.getString('currentUiLanguageCode'));
    463     },
    464 
    465     /**
    466      * Handles languageOptionsList's save event.
    467      * @param {Event} e Save event.
    468      * @private
    469      */
    470     handleLanguageOptionsListSave_: function(e) {
    471       if (cr.isChromeOS) {
    472         // Sort the preload engines per the saved languages before save.
    473         this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
    474         this.savePreloadEnginesPref_();
    475       }
    476     },
    477 
    478     /**
    479      * Sorts preloadEngines_ by languageOptionsList's order.
    480      * @param {Array} preloadEngines List of preload engines.
    481      * @return {Array} Returns sorted preloadEngines.
    482      * @private
    483      */
    484     sortPreloadEngines_: function(preloadEngines) {
    485       // For instance, suppose we have two languages and associated input
    486       // methods:
    487       //
    488       // - Korean: hangul
    489       // - Chinese: pinyin
    490       //
    491       // The preloadEngines preference should look like "hangul,pinyin".
    492       // If the user reverse the order, the preference should be reorderd
    493       // to "pinyin,hangul".
    494       var languageOptionsList = $('language-options-list');
    495       var languageCodes = languageOptionsList.getLanguageCodes();
    496 
    497       // Convert the list into a dictonary for simpler lookup.
    498       var preloadEngineSet = {};
    499       for (var i = 0; i < preloadEngines.length; i++) {
    500         preloadEngineSet[preloadEngines[i]] = true;
    501       }
    502 
    503       // Create the new preload engine list per the language codes.
    504       var newPreloadEngines = [];
    505       for (var i = 0; i < languageCodes.length; i++) {
    506         var languageCode = languageCodes[i];
    507         var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
    508             languageCode];
    509         if (!inputMethodIds)
    510           continue;
    511 
    512         // Check if we have active input methods associated with the language.
    513         for (var j = 0; j < inputMethodIds.length; j++) {
    514           var inputMethodId = inputMethodIds[j];
    515           if (inputMethodId in preloadEngineSet) {
    516             // If we have, add it to the new engine list.
    517             newPreloadEngines.push(inputMethodId);
    518             // And delete it from the set. This is necessary as one input
    519             // method can be associated with more than one language thus
    520             // we should avoid having duplicates in the new list.
    521             delete preloadEngineSet[inputMethodId];
    522           }
    523         }
    524       }
    525 
    526       return newPreloadEngines;
    527     },
    528 
    529     /**
    530      * Initializes the map of language code to input method IDs.
    531      * @private
    532      */
    533     initializeLanguageCodeToInputMethodIdsMap_: function() {
    534       var inputMethodList = loadTimeData.getValue('inputMethodList');
    535       for (var i = 0; i < inputMethodList.length; i++) {
    536         var inputMethod = inputMethodList[i];
    537         for (var languageCode in inputMethod.languageCodeSet) {
    538           if (languageCode in this.languageCodeToInputMethodIdsMap_) {
    539             this.languageCodeToInputMethodIdsMap_[languageCode].push(
    540                 inputMethod.id);
    541           } else {
    542             this.languageCodeToInputMethodIdsMap_[languageCode] =
    543                 [inputMethod.id];
    544           }
    545         }
    546       }
    547     },
    548 
    549     /**
    550      * Updates the currently selected language name.
    551      * @param {string} languageCode Language code (ex. "fr").
    552      * @private
    553      */
    554     updateSelectedLanguageName_: function(languageCode) {
    555       var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
    556           languageCode);
    557       var languageDisplayName = languageInfo.displayName;
    558       var languageNativeDisplayName = languageInfo.nativeDisplayName;
    559       var textDirection = languageInfo.textDirection;
    560 
    561       // If the native name is different, add it.
    562       if (languageDisplayName != languageNativeDisplayName) {
    563         languageDisplayName += ' - ' + languageNativeDisplayName;
    564       }
    565 
    566       // Update the currently selected language name.
    567       var languageName = $('language-options-language-name');
    568       languageName.textContent = languageDisplayName;
    569       languageName.dir = textDirection;
    570     },
    571 
    572     /**
    573      * Updates the UI language button.
    574      * @param {string} languageCode Language code (ex. "fr").
    575      * @private
    576      */
    577     updateUiLanguageButton_: function(languageCode) {
    578       var uiLanguageButton = $('language-options-ui-language-button');
    579       var uiLanguageMessage = $('language-options-ui-language-message');
    580       var uiLanguageNotification = $('language-options-ui-notification-bar');
    581 
    582       // Remove the event listener and add it back if useful.
    583       uiLanguageButton.onclick = null;
    584 
    585       // Unhide the language button every time, as it could've been previously
    586       // hidden by a language change.
    587       uiLanguageButton.hidden = false;
    588 
    589       if (languageCode == this.prospectiveUiLanguageCode_) {
    590         uiLanguageMessage.textContent =
    591             loadTimeData.getString('isDisplayedInThisLanguage');
    592         showMutuallyExclusiveNodes(
    593             [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
    594       } else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
    595         if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
    596           // In the guest mode for ChromeOS, changing UI language does not make
    597           // sense because it does not take effect after browser restart.
    598           uiLanguageButton.hidden = true;
    599           uiLanguageMessage.hidden = true;
    600         } else {
    601           uiLanguageButton.textContent =
    602               loadTimeData.getString('displayInThisLanguage');
    603           showMutuallyExclusiveNodes(
    604               [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
    605           uiLanguageButton.onclick = function(e) {
    606             chrome.send('uiLanguageChange', [languageCode]);
    607           };
    608         }
    609       } else {
    610         uiLanguageMessage.textContent =
    611             loadTimeData.getString('cannotBeDisplayedInThisLanguage');
    612         showMutuallyExclusiveNodes(
    613             [uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
    614       }
    615     },
    616 
    617     /**
    618      * Updates the spell check language button.
    619      * @param {string} languageCode Language code (ex. "fr").
    620      * @private
    621      */
    622     updateSpellCheckLanguageButton_: function(languageCode) {
    623       var spellCheckLanguageSection = $('language-options-spellcheck');
    624       var spellCheckLanguageButton =
    625           $('language-options-spell-check-language-button');
    626       var spellCheckLanguageMessage =
    627           $('language-options-spell-check-language-message');
    628       var dictionaryDownloadInProgress =
    629           $('language-options-dictionary-downloading-message');
    630       var dictionaryDownloadFailed =
    631           $('language-options-dictionary-download-failed-message');
    632       var dictionaryDownloadFailHelp =
    633           $('language-options-dictionary-download-fail-help-message');
    634       spellCheckLanguageSection.hidden = false;
    635       spellCheckLanguageMessage.hidden = true;
    636       spellCheckLanguageButton.hidden = true;
    637       dictionaryDownloadInProgress.hidden = true;
    638       dictionaryDownloadFailed.hidden = true;
    639       dictionaryDownloadFailHelp.hidden = true;
    640 
    641       if (languageCode == this.spellCheckDictionary_) {
    642         if (!(languageCode in this.spellcheckDictionaryDownloadStatus_)) {
    643           spellCheckLanguageMessage.textContent =
    644               loadTimeData.getString('isUsedForSpellChecking');
    645           showMutuallyExclusiveNodes(
    646               [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
    647         } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
    648                        DOWNLOAD_STATUS.IN_PROGRESS) {
    649           dictionaryDownloadInProgress.hidden = false;
    650         } else if (this.spellcheckDictionaryDownloadStatus_[languageCode] ==
    651                        DOWNLOAD_STATUS.FAILED) {
    652           spellCheckLanguageSection.hidden = true;
    653           dictionaryDownloadFailed.hidden = false;
    654           if (this.spellcheckDictionaryDownloadFailures_ > 1)
    655             dictionaryDownloadFailHelp.hidden = false;
    656         }
    657       } else if (languageCode in
    658           loadTimeData.getValue('spellCheckLanguageCodeSet')) {
    659         spellCheckLanguageButton.textContent =
    660             loadTimeData.getString('useThisForSpellChecking');
    661         showMutuallyExclusiveNodes(
    662             [spellCheckLanguageButton, spellCheckLanguageMessage], 0);
    663         spellCheckLanguageButton.languageCode = languageCode;
    664       } else if (!languageCode) {
    665         spellCheckLanguageButton.hidden = true;
    666         spellCheckLanguageMessage.hidden = true;
    667       } else {
    668         spellCheckLanguageMessage.textContent =
    669             loadTimeData.getString('cannotBeUsedForSpellChecking');
    670         showMutuallyExclusiveNodes(
    671             [spellCheckLanguageButton, spellCheckLanguageMessage], 1);
    672       }
    673     },
    674 
    675     /**
    676      * Updates the checkbox for stopping translation.
    677      * @param {string} languageCode Language code (ex. "fr").
    678      * @private
    679      */
    680     updateDontTranslateCheckbox_: function(languageCode) {
    681       var div = $('language-options-dont-translate');
    682 
    683       if (!loadTimeData.getBoolean('enableTranslateSettings')) {
    684         div.hidden = true;
    685         return;
    686       }
    687 
    688       // Translation server supports Chinese (Transitional) and Chinese
    689       // (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
    690       // show this preference when general Chinese is selected.
    691       if (languageCode != 'zh') {
    692         div.hidden = false;
    693       } else {
    694         div.hidden = true;
    695         return;
    696       }
    697 
    698       var dontTranslate = div.querySelector('div');
    699       var cannotTranslate = $('cannot-translate-in-this-language');
    700       var nodes = [dontTranslate, cannotTranslate];
    701 
    702       var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
    703       if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
    704         showMutuallyExclusiveNodes(nodes, 0);
    705       } else {
    706         showMutuallyExclusiveNodes(nodes, 1);
    707         return;
    708       }
    709 
    710       var checkbox = $('dont-translate-in-this-language');
    711 
    712       // If the language corresponds to the default target language (in most
    713       // cases, the user's locale language), "Don't Translate" checkbox should
    714       // be always checked.
    715       var defaultTargetLanguage =
    716           loadTimeData.getString('defaultTargetLanguage');
    717       if (convertedLangCode == defaultTargetLanguage) {
    718         checkbox.disabled = true;
    719         checkbox.checked = true;
    720         return;
    721       }
    722 
    723       checkbox.disabled = false;
    724 
    725       var blockedLanguages = this.translateBlockedLanguages_;
    726       var checked = blockedLanguages.indexOf(convertedLangCode) != -1;
    727       checkbox.checked = checked;
    728     },
    729 
    730     /**
    731      * Updates the input method list.
    732      * @param {string} languageCode Language code (ex. "fr").
    733      * @private
    734      */
    735     updateInputMethodList_: function(languageCode) {
    736       // Give one of the checkboxes or buttons focus, if it's specified in the
    737       // URL hash (ex. focus=mozc). Used for automated testing.
    738       var focusInputMethodId = -1;
    739       var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
    740       if (match) {
    741         focusInputMethodId = match[1];
    742       }
    743       // Change the visibility of the input method list. Input methods that
    744       // matches |languageCode| will become visible.
    745       var inputMethodList = $('language-options-input-method-list');
    746       var methods = inputMethodList.querySelectorAll('.input-method');
    747       for (var i = 0; i < methods.length; i++) {
    748         var method = methods[i];
    749         if (languageCode in method.languageCodeSet) {
    750           method.hidden = false;
    751           var input = method.querySelector('input');
    752           // Give it focus if the ID matches.
    753           if (input.inputMethodId == focusInputMethodId) {
    754             input.focus();
    755           }
    756         } else {
    757           method.hidden = true;
    758         }
    759       }
    760 
    761       $('language-options-input-method-none').hidden =
    762           (languageCode in this.languageCodeToInputMethodIdsMap_);
    763 
    764       if (focusInputMethodId == 'add') {
    765         $('language-options-add-button').focus();
    766       }
    767     },
    768 
    769     /**
    770      * Updates the language list in the add language overlay.
    771      * @param {string} languageCode Language code (ex. "fr").
    772      * @private
    773      */
    774     updateLanguageListInAddLanguageOverlay_: function(languageCode) {
    775       // Change the visibility of the language list in the add language
    776       // overlay. Languages that are already active will become invisible,
    777       // so that users don't add the same language twice.
    778       var languageOptionsList = $('language-options-list');
    779       var languageCodes = languageOptionsList.getLanguageCodes();
    780       var languageCodeSet = {};
    781       for (var i = 0; i < languageCodes.length; i++) {
    782         languageCodeSet[languageCodes[i]] = true;
    783       }
    784 
    785       var addLanguageList = $('add-language-overlay-language-list');
    786       var options = addLanguageList.querySelectorAll('option');
    787       assert(options.length > 0);
    788       var selectedFirstItem = false;
    789       for (var i = 0; i < options.length; i++) {
    790         var option = options[i];
    791         option.hidden = option.value in languageCodeSet;
    792         if (!option.hidden && !selectedFirstItem) {
    793           // Select first visible item, otherwise previously selected hidden
    794           // item will be selected by default at the next time.
    795           option.selected = true;
    796           selectedFirstItem = true;
    797         }
    798       }
    799     },
    800 
    801     /**
    802      * Handles preloadEnginesPref change.
    803      * @param {Event} e Change event.
    804      * @private
    805      */
    806     handlePreloadEnginesPrefChange_: function(e) {
    807       var value = e.value.value;
    808       this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
    809       this.updateCheckboxesFromPreloadEngines_();
    810       $('language-options-list').updateDeletable();
    811     },
    812 
    813     /**
    814      * Handles enabledExtensionImePref change.
    815      * @param {Event} e Change event.
    816      * @private
    817      */
    818     handleEnabledExtensionsPrefChange_: function(e) {
    819       var value = e.value.value;
    820       this.enabledExtensionImes_ = value.split(',');
    821       this.updateCheckboxesFromEnabledExtensions_();
    822     },
    823 
    824     /**
    825      * Handles don't-translate checkbox's click event.
    826      * @param {Event} e Click event.
    827      * @private
    828      */
    829     handleDontTranslateCheckboxClick_: function(e) {
    830       var checkbox = e.target;
    831       var checked = checkbox.checked;
    832 
    833       var languageOptionsList = $('language-options-list');
    834       var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
    835 
    836       if (checked)
    837         this.addBlockedLanguage_(selectedLanguageCode);
    838       else
    839         this.removeBlockedLanguage_(selectedLanguageCode);
    840     },
    841 
    842     /**
    843      * Handles input method checkbox's click event.
    844      * @param {Event} e Click event.
    845      * @private
    846      */
    847     handleCheckboxClick_: function(e) {
    848       var checkbox = e.target;
    849 
    850       if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
    851         this.updateEnabledExtensionsFromCheckboxes_();
    852         this.saveEnabledExtensionPref_();
    853         return;
    854       }
    855       if (this.preloadEngines_.length == 1 && !checkbox.checked) {
    856         // Don't allow disabling the last input method.
    857         this.showNotification_(
    858             loadTimeData.getString('pleaseAddAnotherInputMethod'),
    859             loadTimeData.getString('okButton'));
    860         checkbox.checked = true;
    861         return;
    862       }
    863       if (checkbox.checked) {
    864         chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
    865       } else {
    866         chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
    867       }
    868       this.updatePreloadEnginesFromCheckboxes_();
    869       this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
    870       this.savePreloadEnginesPref_();
    871     },
    872 
    873     handleAddLanguageOkButtonClick_: function() {
    874       var languagesSelect = $('add-language-overlay-language-list');
    875       var selectedIndex = languagesSelect.selectedIndex;
    876       if (selectedIndex >= 0) {
    877         var selection = languagesSelect.options[selectedIndex];
    878         var langCode = String(selection.value);
    879         $('language-options-list').addLanguage(langCode);
    880         this.addBlockedLanguage_(langCode);
    881         OptionsPage.closeOverlay();
    882       }
    883     },
    884 
    885     /**
    886      * Checks if languageCode is deletable or not.
    887      * @param {string} languageCode the languageCode to check for deletability.
    888      */
    889     languageIsDeletable: function(languageCode) {
    890       // Don't allow removing the language if it's a UI language.
    891       if (languageCode == this.prospectiveUiLanguageCode_)
    892         return false;
    893       return (!cr.isChromeOS ||
    894               this.canDeleteLanguage_(languageCode));
    895     },
    896 
    897     /**
    898      * Handles browse.enable_spellchecking change.
    899      * @param {Event} e Change event.
    900      * @private
    901      */
    902     updateEnableSpellCheck_: function() {
    903        var value = !$('enable-spell-check').checked;
    904        $('language-options-spell-check-language-button').disabled = value;
    905        if (!cr.IsMac)
    906          $('edit-dictionary-button').hidden = value;
    907      },
    908 
    909     /**
    910      * Handles translateBlockedLanguagesPref change.
    911      * @param {Event} e Change event.
    912      * @private
    913      */
    914     handleTranslateBlockedLanguagesPrefChange_: function(e) {
    915       var languageOptionsList = $('language-options-list');
    916       var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
    917       this.translateBlockedLanguages_ = e.value.value;
    918       this.updateDontTranslateCheckbox_(selectedLanguageCode);
    919     },
    920 
    921     /**
    922      * Handles spellCheckDictionaryPref change.
    923      * @param {Event} e Change event.
    924      * @private
    925      */
    926     handleSpellCheckDictionaryPrefChange_: function(e) {
    927       var languageCode = e.value.value;
    928       this.spellCheckDictionary_ = languageCode;
    929       var languageOptionsList = $('language-options-list');
    930       var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
    931       if (!cr.isMac)
    932         this.updateSpellCheckLanguageButton_(selectedLanguageCode);
    933     },
    934 
    935     /**
    936      * Handles spellCheckLanguageButton click.
    937      * @param {Event} e Click event.
    938      * @private
    939      */
    940     handleSpellCheckLanguageButtonClick_: function(e) {
    941       var languageCode = e.target.languageCode;
    942       // Save the preference.
    943       Preferences.setStringPref(SPELL_CHECK_DICTIONARY_PREF,
    944                                 languageCode, true);
    945       chrome.send('spellCheckLanguageChange', [languageCode]);
    946     },
    947 
    948     /**
    949      * Checks whether it's possible to remove the language specified by
    950      * languageCode and returns true if possible. This function returns false
    951      * if the removal causes the number of preload engines to be zero.
    952      *
    953      * @param {string} languageCode Language code (ex. "fr").
    954      * @return {boolean} Returns true on success.
    955      * @private
    956      */
    957     canDeleteLanguage_: function(languageCode) {
    958       // First create the set of engines to be removed from input methods
    959       // associated with the language code.
    960       var enginesToBeRemovedSet = {};
    961       var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
    962 
    963       // If this language doesn't have any input methods, it can be deleted.
    964       if (!inputMethodIds)
    965         return true;
    966 
    967       for (var i = 0; i < inputMethodIds.length; i++) {
    968         enginesToBeRemovedSet[inputMethodIds[i]] = true;
    969       }
    970 
    971       // Then eliminate engines that are also used for other active languages.
    972       // For instance, if "xkb:us::eng" is used for both English and Filipino.
    973       var languageCodes = $('language-options-list').getLanguageCodes();
    974       for (var i = 0; i < languageCodes.length; i++) {
    975         // Skip the target language code.
    976         if (languageCodes[i] == languageCode) {
    977           continue;
    978         }
    979         // Check if input methods used in this language are included in
    980         // enginesToBeRemovedSet. If so, eliminate these from the set, so
    981         // we don't remove this time.
    982         var inputMethodIdsForAnotherLanguage =
    983             this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
    984         if (!inputMethodIdsForAnotherLanguage)
    985           continue;
    986 
    987         for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
    988           var inputMethodId = inputMethodIdsForAnotherLanguage[j];
    989           if (inputMethodId in enginesToBeRemovedSet) {
    990             delete enginesToBeRemovedSet[inputMethodId];
    991           }
    992         }
    993       }
    994 
    995       // Update the preload engine list with the to-be-removed set.
    996       var newPreloadEngines = [];
    997       for (var i = 0; i < this.preloadEngines_.length; i++) {
    998         if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
    999           newPreloadEngines.push(this.preloadEngines_[i]);
   1000         }
   1001       }
   1002       // Don't allow this operation if it causes the number of preload
   1003       // engines to be zero.
   1004       return (newPreloadEngines.length > 0);
   1005     },
   1006 
   1007     /**
   1008      * Saves the enabled extension preference.
   1009      * @private
   1010      */
   1011     saveEnabledExtensionPref_: function() {
   1012       Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
   1013                                 this.enabledExtensionImes_.join(','), true);
   1014     },
   1015 
   1016     /**
   1017      * Updates the checkboxes in the input method list from the enabled
   1018      * extensions preference.
   1019      * @private
   1020      */
   1021     updateCheckboxesFromEnabledExtensions_: function() {
   1022       // Convert the list into a dictonary for simpler lookup.
   1023       var dictionary = {};
   1024       for (var i = 0; i < this.enabledExtensionImes_.length; i++)
   1025         dictionary[this.enabledExtensionImes_[i]] = true;
   1026 
   1027       var inputMethodList = $('language-options-input-method-list');
   1028       var checkboxes = inputMethodList.querySelectorAll('input');
   1029       for (var i = 0; i < checkboxes.length; i++) {
   1030         if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
   1031           checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
   1032       }
   1033     },
   1034 
   1035     /**
   1036      * Updates the enabled extensions preference from the checkboxes in the
   1037      * input method list.
   1038      * @private
   1039      */
   1040     updateEnabledExtensionsFromCheckboxes_: function() {
   1041       this.enabledExtensionImes_ = [];
   1042       var inputMethodList = $('language-options-input-method-list');
   1043       var checkboxes = inputMethodList.querySelectorAll('input');
   1044       for (var i = 0; i < checkboxes.length; i++) {
   1045         if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
   1046           if (checkboxes[i].checked)
   1047             this.enabledExtensionImes_.push(checkboxes[i].inputMethodId);
   1048         }
   1049       }
   1050     },
   1051 
   1052     /**
   1053      * Saves the preload engines preference.
   1054      * @private
   1055      */
   1056     savePreloadEnginesPref_: function() {
   1057       Preferences.setStringPref(PRELOAD_ENGINES_PREF,
   1058                                 this.preloadEngines_.join(','), true);
   1059     },
   1060 
   1061     /**
   1062      * Updates the checkboxes in the input method list from the preload
   1063      * engines preference.
   1064      * @private
   1065      */
   1066     updateCheckboxesFromPreloadEngines_: function() {
   1067       // Convert the list into a dictonary for simpler lookup.
   1068       var dictionary = {};
   1069       for (var i = 0; i < this.preloadEngines_.length; i++) {
   1070         dictionary[this.preloadEngines_[i]] = true;
   1071       }
   1072 
   1073       var inputMethodList = $('language-options-input-method-list');
   1074       var checkboxes = inputMethodList.querySelectorAll('input');
   1075       for (var i = 0; i < checkboxes.length; i++) {
   1076         if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
   1077           checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
   1078       }
   1079       var configureButtons = inputMethodList.querySelectorAll('button');
   1080       for (var i = 0; i < configureButtons.length; i++) {
   1081         configureButtons[i].hidden =
   1082             !(configureButtons[i].inputMethodId in dictionary);
   1083       }
   1084     },
   1085 
   1086     /**
   1087      * Updates the preload engines preference from the checkboxes in the
   1088      * input method list.
   1089      * @private
   1090      */
   1091     updatePreloadEnginesFromCheckboxes_: function() {
   1092       this.preloadEngines_ = [];
   1093       var inputMethodList = $('language-options-input-method-list');
   1094       var checkboxes = inputMethodList.querySelectorAll('input');
   1095       for (var i = 0; i < checkboxes.length; i++) {
   1096         if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
   1097           if (checkboxes[i].checked)
   1098             this.preloadEngines_.push(checkboxes[i].inputMethodId);
   1099         }
   1100       }
   1101       var languageOptionsList = $('language-options-list');
   1102       languageOptionsList.updateDeletable();
   1103     },
   1104 
   1105     /**
   1106      * Filters bad preload engines in case bad preload engines are
   1107      * stored in the preference. Removes duplicates as well.
   1108      * @param {Array} preloadEngines List of preload engines.
   1109      * @private
   1110      */
   1111     filterBadPreloadEngines_: function(preloadEngines) {
   1112       // Convert the list into a dictonary for simpler lookup.
   1113       var dictionary = {};
   1114       var list = loadTimeData.getValue('inputMethodList');
   1115       for (var i = 0; i < list.length; i++) {
   1116         dictionary[list[i].id] = true;
   1117       }
   1118 
   1119       var enabledPreloadEngines = [];
   1120       var seen = {};
   1121       for (var i = 0; i < preloadEngines.length; i++) {
   1122         // Check if the preload engine is present in the
   1123         // dictionary, and not duplicate. Otherwise, skip it.
   1124         // Component Extension IME should be handled same as preloadEngines and
   1125         // "_comp_" is the special prefix of its ID.
   1126         if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) ||
   1127             /^_comp_/.test(preloadEngines[i])) {
   1128           enabledPreloadEngines.push(preloadEngines[i]);
   1129           seen[preloadEngines[i]] = true;
   1130         }
   1131       }
   1132       return enabledPreloadEngines;
   1133     },
   1134 
   1135     // TODO(kochi): This is an adapted copy from new_tab.js.
   1136     // If this will go as final UI, refactor this to share the component with
   1137     // new new tab page.
   1138     /**
   1139      * Shows notification
   1140      * @private
   1141      */
   1142     notificationTimeout_: null,
   1143     showNotification_: function(text, actionText, opt_delay) {
   1144       var notificationElement = $('notification');
   1145       var actionLink = notificationElement.querySelector('.link-color');
   1146       var delay = opt_delay || 10000;
   1147 
   1148       function show() {
   1149         window.clearTimeout(this.notificationTimeout_);
   1150         notificationElement.classList.add('show');
   1151         document.body.classList.add('notification-shown');
   1152       }
   1153 
   1154       function hide() {
   1155         window.clearTimeout(this.notificationTimeout_);
   1156         notificationElement.classList.remove('show');
   1157         document.body.classList.remove('notification-shown');
   1158         // Prevent tabbing to the hidden link.
   1159         actionLink.tabIndex = -1;
   1160         // Setting tabIndex to -1 only prevents future tabbing to it. If,
   1161         // however, the user switches window or a tab and then moves back to
   1162         // this tab the element may gain focus. We therefore make sure that we
   1163         // blur the element so that the element focus is not restored when
   1164         // coming back to this window.
   1165         actionLink.blur();
   1166       }
   1167 
   1168       function delayedHide() {
   1169         this.notificationTimeout_ = window.setTimeout(hide, delay);
   1170       }
   1171 
   1172       notificationElement.firstElementChild.textContent = text;
   1173       actionLink.textContent = actionText;
   1174 
   1175       actionLink.onclick = hide;
   1176       actionLink.onkeydown = function(e) {
   1177         if (e.keyIdentifier == 'Enter') {
   1178           hide();
   1179         }
   1180       };
   1181       notificationElement.onmouseover = show;
   1182       notificationElement.onmouseout = delayedHide;
   1183       actionLink.onfocus = show;
   1184       actionLink.onblur = delayedHide;
   1185       // Enable tabbing to the link now that it is shown.
   1186       actionLink.tabIndex = 0;
   1187 
   1188       show();
   1189       delayedHide();
   1190     },
   1191 
   1192     onDictionaryDownloadBegin_: function(languageCode) {
   1193       this.spellcheckDictionaryDownloadStatus_[languageCode] =
   1194           DOWNLOAD_STATUS.IN_PROGRESS;
   1195       if (!cr.isMac &&
   1196           languageCode ==
   1197               $('language-options-list').getSelectedLanguageCode()) {
   1198         this.updateSpellCheckLanguageButton_(languageCode);
   1199       }
   1200     },
   1201 
   1202     onDictionaryDownloadSuccess_: function(languageCode) {
   1203       delete this.spellcheckDictionaryDownloadStatus_[languageCode];
   1204       this.spellcheckDictionaryDownloadFailures_ = 0;
   1205       if (!cr.isMac &&
   1206           languageCode ==
   1207               $('language-options-list').getSelectedLanguageCode()) {
   1208         this.updateSpellCheckLanguageButton_(languageCode);
   1209       }
   1210     },
   1211 
   1212     onDictionaryDownloadFailure_: function(languageCode) {
   1213       this.spellcheckDictionaryDownloadStatus_[languageCode] =
   1214           DOWNLOAD_STATUS.FAILED;
   1215       this.spellcheckDictionaryDownloadFailures_++;
   1216       if (!cr.isMac &&
   1217           languageCode ==
   1218               $('language-options-list').getSelectedLanguageCode()) {
   1219         this.updateSpellCheckLanguageButton_(languageCode);
   1220       }
   1221     },
   1222 
   1223     /*
   1224      * Converts the language code for Translation. There are some differences
   1225      * between the language set for Translation and that for Accept-Language.
   1226      * @param {string} languageCode The language code like 'fr'.
   1227      * @return {string} The converted language code.
   1228      * @private
   1229      */
   1230     convertLangCodeForTranslation_: function(languageCode) {
   1231       var tokens = languageCode.split('-');
   1232       var main = tokens[0];
   1233 
   1234       // See also: chrome/renderer/translate/translate_helper.cc.
   1235       var synonyms = {
   1236         'nb': 'no',
   1237         'he': 'iw',
   1238         'jv': 'jw',
   1239         'fil': 'tl',
   1240       };
   1241 
   1242       if (main in synonyms) {
   1243         return synonyms[main];
   1244       } else if (main == 'zh') {
   1245         // In Translation, general Chinese is not used, and the sub code is
   1246         // necessary as a language code for Translate server.
   1247         return languageCode;
   1248       }
   1249 
   1250       return main;
   1251     },
   1252   };
   1253 
   1254   /**
   1255    * Shows the node at |index| in |nodes|, hides all others.
   1256    * @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
   1257    * @param {number} index The index of |nodes| to show.
   1258    */
   1259   function showMutuallyExclusiveNodes(nodes, index) {
   1260     assert(index >= 0 && index < nodes.length);
   1261     for (var i = 0; i < nodes.length; ++i) {
   1262       assert(nodes[i] instanceof HTMLElement);  // TODO(dbeam): Ignore null?
   1263       nodes[i].hidden = i != index;
   1264     }
   1265   }
   1266 
   1267   /**
   1268    * Chrome callback for when the UI language preference is saved.
   1269    * @param {string} languageCode The newly selected language to use.
   1270    */
   1271   LanguageOptions.uiLanguageSaved = function(languageCode) {
   1272     this.prospectiveUiLanguageCode_ = languageCode;
   1273 
   1274     // If the user is no longer on the same language code, ignore.
   1275     if ($('language-options-list').getSelectedLanguageCode() != languageCode)
   1276       return;
   1277 
   1278     // Special case for when a user changes to a different language, and changes
   1279     // back to the same language without having restarted Chrome or logged
   1280     // in/out of ChromeOS.
   1281     if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
   1282       LanguageOptions.getInstance().currentLocaleWasReselected();
   1283       return;
   1284     }
   1285 
   1286     // Otherwise, show a notification telling the user that their changes will
   1287     // only take effect after restart.
   1288     showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
   1289                                 $('language-options-ui-notification-bar')], 1);
   1290   };
   1291 
   1292   LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
   1293     LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
   1294   };
   1295 
   1296   LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
   1297     LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
   1298   };
   1299 
   1300   LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
   1301     LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
   1302   };
   1303 
   1304   LanguageOptions.onComponentManagerInitialized = function(componentImes) {
   1305     LanguageOptions.getInstance().appendComponentExtensionIme_(componentImes);
   1306   };
   1307 
   1308   // Export
   1309   return {
   1310     LanguageOptions: LanguageOptions
   1311   };
   1312 });
   1313