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