blob: 0e85622be1123da3591cd128bf9746a7c3100865 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// TODO(kochi): Generalize the notification as a component and put it
// in js/cr/ui/notification.js .
cr.define('options', function() {
/** @const */ var Page = cr.ui.pageManager.Page;
/** @const */ var PageManager = cr.ui.pageManager.PageManager;
/** @const */ var LanguageList = options.LanguageList;
/** @const */ var ThirdPartyImeConfirmOverlay =
options.ThirdPartyImeConfirmOverlay;
/**
* Spell check dictionary download status.
* @type {Enum}
*/
/** @const*/ var DOWNLOAD_STATUS = {
IN_PROGRESS: 1,
FAILED: 2
};
/**
* The preference is a boolean that enables/disables spell checking.
* @type {string}
* @const
*/
var ENABLE_SPELL_CHECK_PREF = 'browser.enable_spellchecking';
/**
* The preference is a CSV string that describes preload engines
* (i.e. active input methods).
* @type {string}
* @const
*/
var PRELOAD_ENGINES_PREF = 'settings.language.preload_engines';
/**
* The preference that lists the extension IMEs that are enabled in the
* language menu.
* @type {string}
* @const
*/
var ENABLED_EXTENSION_IME_PREF = 'settings.language.enabled_extension_imes';
/**
* The preference that lists the languages which are not translated.
* @type {string}
* @const
*/
var TRANSLATE_BLOCKED_LANGUAGES_PREF = 'translate_blocked_languages';
/**
* The preference key that is a list of strings that describes the spellcheck
* dictionary language, like ["en-US", "fr"].
* @type {string}
* @const
*/
var SPELL_CHECK_DICTIONARIES_PREF = 'spellcheck.dictionaries';
/**
* The preference that indicates if the Translate feature is enabled.
* @type {string}
* @const
*/
var ENABLE_TRANSLATE = 'translate.enabled';
/**
* The preference is a boolean that activates/deactivates IME menu on shelf.
* @type {string}
* @const
*/
var ACTIVATE_IME_MENU_PREF = 'settings.language.ime_menu_activated';
/////////////////////////////////////////////////////////////////////////////
// LanguageOptions class:
/**
* Encapsulated handling of ChromeOS language options page.
* @constructor
* @extends {cr.ui.pageManager.Page}
*/
function LanguageOptions(model) {
Page.call(this, 'languages',
loadTimeData.getString('languagePageTabTitle'), 'languagePage');
}
cr.addSingletonGetter(LanguageOptions);
// Inherit LanguageOptions from Page.
LanguageOptions.prototype = {
__proto__: Page.prototype,
/**
* For recording the prospective language (the next locale after relaunch).
* @type {?string}
* @private
*/
prospectiveUiLanguageCode_: null,
/**
* Map from language code to spell check dictionary download status for that
* language.
* @type {!Object}
* @private
*/
spellcheckDictionaryDownloadStatus_: {},
/**
* Number of times a spell check dictionary download failed.
* @type {number}
* @private
*/
spellcheckDictionaryDownloadFailures_: 0,
/**
* The list of preload engines, like ['mozc', 'pinyin'].
* @type {Array}
* @private
*/
preloadEngines_: [],
/**
* The list of extension IMEs that are enabled out of the language menu.
* @type {Array}
* @private
*/
enabledExtensionImes_: [],
/**
* The list of the languages which is not translated.
* @type {Array}
* @private
*/
translateBlockedLanguages_: [],
/**
* The list of the languages supported by Translate server
* @type {Array}
* @private
*/
translateSupportedLanguages_: [],
/**
* The dictionary of currently selected spellcheck dictionary languages,
* like {"en-US": true, "sl-SI": true}.
* @type {!Object}
* @private
*/
spellCheckLanguages_: {},
/**
* The map of language code to input method IDs, like:
* {'ja': ['mozc', 'mozc-jp'], 'zh-CN': ['pinyin'], ...}
* @type {Object}
* @private
*/
languageCodeToInputMethodIdsMap_: {},
/**
* The value that indicates if Translate feature is enabled or not.
* @type {boolean}
* @private
*/
enableTranslate_: false,
/** @override */
initializePage: function() {
Page.prototype.initializePage.call(this);
var languageOptionsList = $('language-options-list');
LanguageList.decorate(languageOptionsList);
languageOptionsList.addEventListener('change',
this.handleLanguageOptionsListChange_.bind(this));
languageOptionsList.addEventListener('save',
this.handleLanguageOptionsListSave_.bind(this));
this.prospectiveUiLanguageCode_ =
loadTimeData.getString('prospectiveUiLanguageCode');
this.addEventListener('visibleChange',
this.handleVisibleChange_.bind(this));
if (cr.isChromeOS) {
this.initializeInputMethodList_();
this.initializeLanguageCodeToInputMethodIdsMap_();
}
var checkbox = $('offer-to-translate-in-this-language');
checkbox.addEventListener('click',
this.handleOfferToTranslateCheckboxClick_.bind(this));
Preferences.getInstance().addEventListener(
TRANSLATE_BLOCKED_LANGUAGES_PREF,
this.handleTranslateBlockedLanguagesPrefChange_.bind(this));
Preferences.getInstance().addEventListener(SPELL_CHECK_DICTIONARIES_PREF,
this.handleSpellCheckDictionariesPrefChange_.bind(this));
Preferences.getInstance().addEventListener(ENABLE_TRANSLATE,
this.handleEnableTranslatePrefChange_.bind(this));
this.translateSupportedLanguages_ = /** @type {Array} */(
loadTimeData.getValue('translateSupportedLanguages'));
// Set up add button.
var onclick = function(e) {
// Add the language without showing the overlay if it's specified in
// the URL hash (ex. lang_add=ja). Used for automated testing.
var match = document.location.hash.match(/\blang_add=([\w-]+)/);
if (match) {
var addLanguageCode = match[1];
$('language-options-list').addLanguage(addLanguageCode);
this.addBlockedLanguage_(addLanguageCode);
} else {
PageManager.showPageByName('addLanguage');
chrome.send('coreOptionsUserMetricsAction',
['Options_Languages_Add']);
}
};
$('language-options-add-button').onclick = onclick.bind(this);
if (!cr.isMac) {
// Set up the button for editing custom spelling dictionary.
$('edit-custom-dictionary-button').onclick = function(e) {
PageManager.showPageByName('editDictionary');
};
$('dictionary-download-retry-button').onclick = function(e) {
chrome.send('retryDictionaryDownload',
[e.currentTarget.languageCode]);
};
}
// Listen to add language dialog ok button.
$('add-language-overlay-ok-button').addEventListener(
'click', this.handleAddLanguageOkButtonClick_.bind(this));
if (!(cr.isMac || cr.isChromeOS)) {
// Handle spell check enable/disable.
Preferences.getInstance().addEventListener(
ENABLE_SPELL_CHECK_PREF, this.updateEnableSpellCheck_.bind(this));
}
// Handle clicks on "Use this language for spell checking" button.
if (!cr.isMac) {
$('spellcheck-language-checkbox').addEventListener(
'change',
this.handleSpellCheckLanguageCheckboxClick_.bind(this));
}
if (cr.isChromeOS) {
$('language-options-ui-restart-button').onclick = function() {
chrome.send('uiLanguageRestart');
};
}
$('language-confirm').onclick =
PageManager.closeOverlay.bind(PageManager);
// Public session users cannot change the locale.
if (cr.isChromeOS && UIAccountTweaks.loggedInAsPublicAccount())
$('language-options-ui-language-section').hidden = true;
// IME menu (CrOS only).
if (cr.isChromeOS) {
// Show the 'activate-ime-menu' checkbox if the flag is tured on.
if (loadTimeData.getBoolean('enableLanguageOptionsImeMenu'))
$('language-options-ime-menu-template').hidden = false;
// Updates the initial checked state of the check box.
Preferences.getInstance().addEventListener(
ACTIVATE_IME_MENU_PREF, this.updateImeMenuCheckbox_.bind(this));
// Listen to check on 'activate-ime-menu' checkbox.
var checkboxImeMenu = $('activate-ime-menu');
checkboxImeMenu.addEventListener('click',
this.handleActivateImeMenuCheckboxClick_.bind(this));
}
},
/**
* Initializes the input method list.
*/
initializeInputMethodList_: function() {
var inputMethodList = $('language-options-input-method-list');
var inputMethodPrototype = $('language-options-input-method-template');
// Add all input methods, but make all of them invisible here. We'll
// change the visibility in handleLanguageOptionsListChange_() based
// on the selected language. Note that we only have less than 100
// input methods, so creating DOM nodes at once here should be ok.
this.appendInputMethodElement_(/** @type {!Array} */(
loadTimeData.getValue('inputMethodList')));
this.appendComponentExtensionIme_(/** @type {!Array} */(
loadTimeData.getValue('componentExtensionImeList')));
this.appendInputMethodElement_(/** @type {!Array} */(
loadTimeData.getValue('extensionImeList')));
// Listen to pref change once the input method list is initialized.
Preferences.getInstance().addEventListener(
PRELOAD_ENGINES_PREF,
this.handlePreloadEnginesPrefChange_.bind(this));
Preferences.getInstance().addEventListener(
ENABLED_EXTENSION_IME_PREF,
this.handleEnabledExtensionsPrefChange_.bind(this));
},
/**
* Appends input method lists based on component extension ime list.
* @param {!Array} componentExtensionImeList A list of input method
* descriptors.
* @private
*/
appendComponentExtensionIme_: function(componentExtensionImeList) {
this.appendInputMethodElement_(componentExtensionImeList);
for (var i = 0; i < componentExtensionImeList.length; i++) {
var inputMethod = componentExtensionImeList[i];
for (var languageCode in inputMethod.languageCodeSet) {
if (languageCode in this.languageCodeToInputMethodIdsMap_) {
this.languageCodeToInputMethodIdsMap_[languageCode].push(
inputMethod.id);
} else {
this.languageCodeToInputMethodIdsMap_[languageCode] =
[inputMethod.id];
}
}
}
},
/**
* Appends input methods into input method list.
* @param {!Array} inputMethods A list of input method descriptors.
* @private
*/
appendInputMethodElement_: function(inputMethods) {
var inputMethodList = $('language-options-input-method-list');
var inputMethodTemplate = $('language-options-input-method-template');
for (var i = 0; i < inputMethods.length; i++) {
var inputMethod = inputMethods[i];
var element = inputMethodTemplate.cloneNode(true);
element.id = '';
element.languageCodeSet = inputMethod.languageCodeSet;
var input = element.querySelector('input');
input.inputMethodId = inputMethod.id;
input.imeProvider = inputMethod.extensionName;
var span = element.querySelector('span');
span.textContent = inputMethod.displayName;
if (inputMethod.optionsPage) {
var button = document.createElement('button');
button.textContent = loadTimeData.getString('configure');
button.inputMethodId = inputMethod.id;
button.onclick = function(inputMethodId, e) {
chrome.send('inputMethodOptionsOpen', [inputMethodId]);
}.bind(this, inputMethod.id);
element.appendChild(button);
}
// Listen to user clicks.
input.addEventListener('click',
this.handleCheckboxClick_.bind(this));
inputMethodList.appendChild(element);
}
},
/**
* Adds a language to the preference 'translate_blocked_languages'. If
* |langCode| is already added, nothing happens. |langCode| is converted
* to a Translate language synonym before added.
* @param {string} langCode A language code like 'en'
* @private
*/
addBlockedLanguage_: function(langCode) {
langCode = this.convertLangCodeForTranslation_(langCode);
if (this.translateBlockedLanguages_.indexOf(langCode) == -1) {
this.translateBlockedLanguages_.push(langCode);
Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
this.translateBlockedLanguages_, true);
}
},
/**
* Removes a language from the preference 'translate_blocked_languages'.
* If |langCode| doesn't exist in the preference, nothing happens.
* |langCode| is converted to a Translate language synonym before removed.
* @param {string} langCode A language code like 'en'
* @private
*/
removeBlockedLanguage_: function(langCode) {
langCode = this.convertLangCodeForTranslation_(langCode);
if (this.translateBlockedLanguages_.indexOf(langCode) != -1) {
this.translateBlockedLanguages_ =
this.translateBlockedLanguages_.filter(
function(langCodeNotTranslated) {
return langCodeNotTranslated != langCode;
});
Preferences.setListPref(TRANSLATE_BLOCKED_LANGUAGES_PREF,
this.translateBlockedLanguages_, true);
}
},
/**
* Handles Page's visible property change event.
* @param {Event} e Property change event.
* @private
*/
handleVisibleChange_: function(e) {
if (this.visible) {
$('language-options-list').redraw();
chrome.send('languageOptionsOpen');
}
},
/**
* Handles languageOptionsList's change event.
* @param {Event} e Change event.
* @private
*/
handleLanguageOptionsListChange_: function(e) {
var languageOptionsList = $('language-options-list');
var languageCode = languageOptionsList.getSelectedLanguageCode();
// If there's no selection, just return.
if (!languageCode)
return;
// Select the language if it's specified in the URL hash (ex. lang=ja).
// Used for automated testing.
var match = document.location.hash.match(/\blang=([\w-]+)/);
if (match) {
var specifiedLanguageCode = match[1];
if (languageOptionsList.selectLanguageByCode(specifiedLanguageCode)) {
languageCode = specifiedLanguageCode;
}
}
this.updateOfferToTranslateCheckbox_(languageCode);
if (cr.isWindows || cr.isChromeOS)
this.updateUiLanguageButton_(languageCode);
this.updateSelectedLanguageName_(languageCode);
if (!cr.isMac)
this.updateSpellCheckLanguageControls_(languageCode);
if (cr.isChromeOS)
this.updateInputMethodList_(languageCode);
this.updateLanguageListInAddLanguageOverlay_();
},
/**
* Handles languageOptionsList's save event.
* @param {Event} e Save event.
* @private
*/
handleLanguageOptionsListSave_: function(e) {
if (cr.isChromeOS) {
// Sort the preload engines per the saved languages before save.
this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
this.savePreloadEnginesPref_();
}
},
/**
* Sorts preloadEngines_ by languageOptionsList's order.
* @param {Array} preloadEngines List of preload engines.
* @return {Array} Returns sorted preloadEngines.
* @private
*/
sortPreloadEngines_: function(preloadEngines) {
// For instance, suppose we have two languages and associated input
// methods:
//
// - Korean: hangul
// - Chinese: pinyin
//
// The preloadEngines preference should look like "hangul,pinyin".
// If the user reverse the order, the preference should be reorderd
// to "pinyin,hangul".
var languageOptionsList = $('language-options-list');
var languageCodes = languageOptionsList.getLanguageCodes();
// Convert the list into a dictonary for simpler lookup.
var preloadEngineSet = {};
for (var i = 0; i < preloadEngines.length; i++) {
preloadEngineSet[preloadEngines[i]] = true;
}
// Create the new preload engine list per the language codes.
var newPreloadEngines = [];
for (var i = 0; i < languageCodes.length; i++) {
var languageCode = languageCodes[i];
var inputMethodIds = this.languageCodeToInputMethodIdsMap_[
languageCode];
if (!inputMethodIds)
continue;
// Check if we have active input methods associated with the language.
for (var j = 0; j < inputMethodIds.length; j++) {
var inputMethodId = inputMethodIds[j];
if (inputMethodId in preloadEngineSet) {
// If we have, add it to the new engine list.
newPreloadEngines.push(inputMethodId);
// And delete it from the set. This is necessary as one input
// method can be associated with more than one language thus
// we should avoid having duplicates in the new list.
delete preloadEngineSet[inputMethodId];
}
}
}
return newPreloadEngines;
},
/**
* Initializes the map of language code to input method IDs.
* @private
*/
initializeLanguageCodeToInputMethodIdsMap_: function() {
var inputMethodList = loadTimeData.getValue('inputMethodList');
for (var i = 0; i < inputMethodList.length; i++) {
var inputMethod = inputMethodList[i];
for (var languageCode in inputMethod.languageCodeSet) {
if (languageCode in this.languageCodeToInputMethodIdsMap_) {
this.languageCodeToInputMethodIdsMap_[languageCode].push(
inputMethod.id);
} else {
this.languageCodeToInputMethodIdsMap_[languageCode] =
[inputMethod.id];
}
}
}
},
/**
* Updates the currently selected language name.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateSelectedLanguageName_: function(languageCode) {
var languageInfo = LanguageList.getLanguageInfoFromLanguageCode(
languageCode);
var languageDisplayName = languageInfo.displayName;
var languageNativeDisplayName = languageInfo.nativeDisplayName;
var textDirection = languageInfo.textDirection;
// If the native name is different, add it.
if (languageDisplayName != languageNativeDisplayName) {
languageDisplayName += ' - ' + languageNativeDisplayName;
}
// Update the currently selected language name.
var languageName = $('language-options-language-name');
languageName.textContent = languageDisplayName;
languageName.dir = textDirection;
},
/**
* Updates the UI language button.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateUiLanguageButton_: function(languageCode) {
var uiLanguageButton = $('language-options-ui-language-button');
var uiLanguageMessage = $('language-options-ui-language-message');
var uiLanguageNotification = $('language-options-ui-notification-bar');
// Remove the event listener and add it back if useful.
uiLanguageButton.onclick = null;
// Unhide the language button every time, as it could've been previously
// hidden by a language change.
uiLanguageButton.hidden = false;
// Hide the controlled setting indicator.
var uiLanguageIndicator = document.querySelector(
'.language-options-contents .controlled-setting-indicator');
uiLanguageIndicator.removeAttribute('controlled-by');
if (languageCode == this.prospectiveUiLanguageCode_) {
uiLanguageMessage.textContent =
loadTimeData.getString('isDisplayedInThisLanguage');
showMutuallyExclusiveNodes(
[uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
} else if (languageCode in loadTimeData.getValue('uiLanguageCodeSet')) {
if (cr.isChromeOS && UIAccountTweaks.loggedInAsGuest()) {
// In the guest mode for ChromeOS, changing UI language does not make
// sense because it does not take effect after browser restart.
uiLanguageButton.hidden = true;
uiLanguageMessage.hidden = true;
} else {
uiLanguageButton.textContent =
loadTimeData.getString('displayInThisLanguage');
if (loadTimeData.valueExists('secondaryUser') &&
loadTimeData.getBoolean('secondaryUser')) {
uiLanguageButton.disabled = true;
uiLanguageIndicator.setAttribute('controlled-by', 'shared');
} else {
uiLanguageButton.onclick = function(e) {
chrome.send('uiLanguageChange', [languageCode]);
};
}
showMutuallyExclusiveNodes(
[uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 0);
}
} else {
uiLanguageMessage.textContent =
loadTimeData.getString('cannotBeDisplayedInThisLanguage');
showMutuallyExclusiveNodes(
[uiLanguageButton, uiLanguageMessage, uiLanguageNotification], 1);
}
},
/**
* Updates the spell check language button/checkbox, dictionary download
* dialog, and the "Enable spell checking" checkbox.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateSpellCheckLanguageControls_: function(languageCode) {
assert(languageCode);
var spellCheckLanguageSection = $('language-options-spellcheck');
var spellCheckLanguageCheckboxContainer =
$('spellcheck-language-checkbox-container');
var spellCheckLanguageCheckbox = $('spellcheck-language-checkbox');
var spellCheckLanguageMessage = $('spellcheck-language-message');
var dictionaryDownloadInProgress =
$('language-options-dictionary-downloading-message');
var dictionaryDownloadFailed =
$('language-options-dictionary-download-failed-message');
var dictionaryDownloadFailHelp =
$('language-options-dictionary-download-fail-help-message');
spellCheckLanguageSection.hidden = false;
spellCheckLanguageMessage.hidden = true;
spellCheckLanguageCheckboxContainer.hidden = true;
dictionaryDownloadInProgress.hidden = true;
dictionaryDownloadFailed.hidden = true;
dictionaryDownloadFailHelp.hidden = true;
spellCheckLanguageCheckbox.checked = false;
var canBeUsedForSpellchecking =
languageCode in loadTimeData.getValue('spellCheckLanguageCodeSet');
if (!canBeUsedForSpellchecking) {
spellCheckLanguageMessage.textContent =
loadTimeData.getString('cannotBeUsedForSpellChecking');
spellCheckLanguageMessage.hidden = false;
return;
}
var isUsedForSpellchecking = languageCode in this.spellCheckLanguages_;
spellCheckLanguageCheckbox.languageCode = languageCode;
spellCheckLanguageCheckbox.checked = isUsedForSpellchecking;
spellCheckLanguageCheckboxContainer.hidden = false;
switch (this.spellcheckDictionaryDownloadStatus_[languageCode]) {
case DOWNLOAD_STATUS.IN_PROGRESS:
dictionaryDownloadInProgress.hidden = false;
break;
case DOWNLOAD_STATUS.FAILED:
showMutuallyExclusiveNodes(
[spellCheckLanguageSection, dictionaryDownloadFailed], 1);
if (this.spellcheckDictionaryDownloadFailures_ > 1)
dictionaryDownloadFailHelp.hidden = false;
$('dictionary-download-retry-button').languageCode = languageCode;
break;
}
var areNoLanguagesSelected =
Object.keys(this.spellCheckLanguages_).length == 0;
var usesSystemSpellchecker = !$('enable-spellcheck-container');
var isSpellcheckingEnabled = usesSystemSpellchecker ||
$('enable-spellcheck').checked;
$('edit-custom-dictionary-button').hidden =
areNoLanguagesSelected || !isSpellcheckingEnabled;
},
/**
* Updates the checkbox for stopping translation.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateOfferToTranslateCheckbox_: function(languageCode) {
var div = $('language-options-offer-to-translate');
// Translation server supports Chinese (Transitional) and Chinese
// (Simplified) but not 'general' Chinese. To avoid ambiguity, we don't
// show this preference when general Chinese is selected.
if (languageCode != 'zh') {
div.hidden = false;
} else {
div.hidden = true;
return;
}
var offerToTranslate = div.querySelector('div');
var cannotTranslate = $('cannot-translate-in-this-language');
var nodes = [offerToTranslate, cannotTranslate];
var convertedLangCode = this.convertLangCodeForTranslation_(languageCode);
if (this.translateSupportedLanguages_.indexOf(convertedLangCode) != -1) {
showMutuallyExclusiveNodes(nodes, 0);
} else {
showMutuallyExclusiveNodes(nodes, 1);
return;
}
var checkbox = $('offer-to-translate-in-this-language');
if (!this.enableTranslate_) {
checkbox.disabled = true;
checkbox.checked = false;
return;
}
// If the language corresponds to the default target language (in most
// cases, the user's locale language), "Offer to translate" checkbox
// should be always unchecked.
var defaultTargetLanguage =
loadTimeData.getString('defaultTargetLanguage');
if (convertedLangCode == defaultTargetLanguage) {
checkbox.disabled = true;
checkbox.checked = false;
return;
}
checkbox.disabled = false;
var blockedLanguages = this.translateBlockedLanguages_;
var checked = blockedLanguages.indexOf(convertedLangCode) == -1;
checkbox.checked = checked;
},
/**
* Updates the input method list.
* @param {string} languageCode Language code (ex. "fr").
* @private
*/
updateInputMethodList_: function(languageCode) {
// Give one of the checkboxes or buttons focus, if it's specified in the
// URL hash (ex. focus=mozc). Used for automated testing.
var focusInputMethodId = -1;
var match = document.location.hash.match(/\bfocus=([\w:-]+)\b/);
if (match) {
focusInputMethodId = match[1];
}
// Change the visibility of the input method list. Input methods that
// matches |languageCode| will become visible.
var inputMethodList = $('language-options-input-method-list');
var methods = inputMethodList.querySelectorAll('.input-method');
for (var i = 0; i < methods.length; i++) {
var method = methods[i];
if (languageCode in method.languageCodeSet) {
method.hidden = false;
var input = method.querySelector('input');
// Give it focus if the ID matches.
if (input.inputMethodId == focusInputMethodId) {
input.focus();
}
} else {
method.hidden = true;
}
}
$('language-options-input-method-none').hidden =
(languageCode in this.languageCodeToInputMethodIdsMap_);
if (focusInputMethodId == 'add') {
$('language-options-add-button').focus();
}
},
/**
* Updates the language list in the add language overlay.
* @private
*/
updateLanguageListInAddLanguageOverlay_: function() {
// Change the visibility of the language list in the add language
// overlay. Languages that are already active will become invisible,
// so that users don't add the same language twice.
var languageOptionsList = $('language-options-list');
var languageCodes = languageOptionsList.getLanguageCodes();
var languageCodeSet = {};
for (var i = 0; i < languageCodes.length; i++) {
languageCodeSet[languageCodes[i]] = true;
}
var addLanguageList = $('add-language-overlay-language-list');
var options = addLanguageList.querySelectorAll('option');
assert(options.length > 0);
var selectedFirstItem = false;
for (var i = 0; i < options.length; i++) {
var option = options[i];
option.hidden = option.value in languageCodeSet;
if (!option.hidden && !selectedFirstItem) {
// Select first visible item, otherwise previously selected hidden
// item will be selected by default at the next time.
option.selected = true;
selectedFirstItem = true;
}
}
},
/**
* Handles preloadEnginesPref change.
* @param {Event} e Change event.
* @private
*/
handlePreloadEnginesPrefChange_: function(e) {
var value = e.value.value;
this.preloadEngines_ = this.filterBadPreloadEngines_(value.split(','));
this.updateCheckboxesFromPreloadEngines_();
$('language-options-list').updateDeletable();
},
/**
* Handles enabledExtensionImePref change.
* @param {Event} e Change event.
* @private
*/
handleEnabledExtensionsPrefChange_: function(e) {
var value = e.value.value;
this.enabledExtensionImes_ = value.split(',');
this.updateCheckboxesFromEnabledExtensions_();
},
/**
* Handles offer-to-translate checkbox's click event.
* @param {Event} e Click event.
* @private
*/
handleOfferToTranslateCheckboxClick_: function(e) {
var checkbox = e.target;
var checked = checkbox.checked;
var languageOptionsList = $('language-options-list');
var selectedLanguageCode = languageOptionsList.getSelectedLanguageCode();
if (checked)
this.removeBlockedLanguage_(selectedLanguageCode);
else
this.addBlockedLanguage_(selectedLanguageCode);
},
/**
* Handles input method checkbox's click event.
* @param {Event} e Click event.
* @private
*/
handleCheckboxClick_: function(e) {
var checkbox = assertInstanceof(e.target, Element);
// Third party IMEs require additional confirmation prior to enabling due
// to privacy risk.
if (/^_ext_ime_/.test(checkbox.inputMethodId) && checkbox.checked) {
var confirmationCallback = this.handleCheckboxUpdate_.bind(this,
checkbox);
var cancellationCallback = function() {
checkbox.checked = false;
};
ThirdPartyImeConfirmOverlay.showConfirmationDialog({
extension: checkbox.imeProvider,
confirm: confirmationCallback,
cancel: cancellationCallback
});
} else {
this.handleCheckboxUpdate_(checkbox);
}
chrome.send('coreOptionsUserMetricsAction',
['Options_Languages_InputMethodCheckbox' +
(checkbox.checked ? '_Enable' : '_Disable')]);
},
/**
* Updates active IMEs based on change in state of a checkbox for an input
* method.
* @param {!Element} checkbox Updated checkbox element.
* @private
*/
handleCheckboxUpdate_: function(checkbox) {
if (checkbox.inputMethodId.match(/^_ext_ime_/)) {
this.updateEnabledExtensionsFromCheckboxes_();
this.saveEnabledExtensionPref_();
return;
}
if (this.preloadEngines_.length == 1 && !checkbox.checked) {
// Don't allow disabling the last input method.
this.showNotification_(
loadTimeData.getString('pleaseAddAnotherInputMethod'),
loadTimeData.getString('okButton'));
checkbox.checked = true;
return;
}
if (checkbox.checked) {
chrome.send('inputMethodEnable', [checkbox.inputMethodId]);
} else {
chrome.send('inputMethodDisable', [checkbox.inputMethodId]);
}
this.updatePreloadEnginesFromCheckboxes_();
this.preloadEngines_ = this.sortPreloadEngines_(this.preloadEngines_);
this.savePreloadEnginesPref_();
},
/**
* Handles clicks on the "OK" button of the "Add language" dialog.
* @param {Event} e Click event.
* @private
*/
handleAddLanguageOkButtonClick_: function(e) {
var languagesSelect = $('add-language-overlay-language-list');
var selectedIndex = languagesSelect.selectedIndex;
if (selectedIndex >= 0) {
var selection = languagesSelect.options[selectedIndex];
var langCode = String(selection.value);
$('language-options-list').addLanguage(langCode);
this.addBlockedLanguage_(langCode);
PageManager.closeOverlay();
}
},
/**
* Checks if languageCode is deletable or not.
* @param {string} languageCode the languageCode to check for deletability.
*/
languageIsDeletable: function(languageCode) {
// Don't allow removing the language if it's a UI language.
if (languageCode == this.prospectiveUiLanguageCode_)
return false;
return (!cr.isChromeOS ||
this.canDeleteLanguage_(languageCode));
},
/**
* Handles browser.enable_spellchecking change.
* @param {Event} e Change event.
* @private
*/
updateEnableSpellCheck_: function(e) {
var value = !$('enable-spellcheck').checked;
var languageControl = $('spellcheck-language-checkbox');
languageControl.disabled = value;
if (!cr.isMac)
$('edit-custom-dictionary-button').hidden = value;
},
/**
* Handles translateBlockedLanguagesPref change.
* @param {Event} e Change event.
* @private
*/
handleTranslateBlockedLanguagesPrefChange_: function(e) {
this.translateBlockedLanguages_ = e.value.value;
this.updateOfferToTranslateCheckbox_(
$('language-options-list').getSelectedLanguageCode());
},
/**
* Updates spellcheck dictionary UI (checkboxes, buttons, and labels) when
* preferences change.
* @param {Event} e Preference change event where e.value.value is the list
* of languages currently used for spellchecking.
* @private
*/
handleSpellCheckDictionariesPrefChange_: function(e) {
if (cr.isMac)
return;
var languages = e.value.value;
this.spellCheckLanguages_ = {};
for (var i = 0; i < languages.length; i++) {
this.spellCheckLanguages_[languages[i]] = true;
}
this.updateSpellCheckLanguageControls_(
$('language-options-list').getSelectedLanguageCode());
},
/**
* Handles translate.enabled change.
* @param {Event} e Change event.
* @private
*/
handleEnableTranslatePrefChange_: function(e) {
var enabled = e.value.value;
this.enableTranslate_ = enabled;
this.updateOfferToTranslateCheckbox_(
$('language-options-list').getSelectedLanguageCode());
},
/**
* Updates the spellcheck.dictionaries preference with the currently
* selected language codes.
* @param {Event} e Click event. e.currentTarget represents the "Use this
* language for spellchecking" checkbox.
* @private
*/
handleSpellCheckLanguageCheckboxClick_: function(e) {
var languageCode = e.currentTarget.languageCode;
if (e.currentTarget.checked)
this.spellCheckLanguages_[languageCode] = true;
else
delete this.spellCheckLanguages_[languageCode];
var languageCodes = Object.keys(this.spellCheckLanguages_);
Preferences.setListPref(SPELL_CHECK_DICTIONARIES_PREF,
languageCodes, true);
// The spellCheckLanguageChange argument is only used for logging.
chrome.send('spellCheckLanguageChange', [languageCodes.join(',')]);
chrome.send('coreOptionsUserMetricsAction',
['Options_Languages_SpellCheck']);
},
/**
* Checks whether it's possible to remove the language specified by
* languageCode and returns true if possible. This function returns false
* if the removal causes the number of preload engines to be zero.
*
* @param {string} languageCode Language code (ex. "fr").
* @return {boolean} Returns true on success.
* @private
*/
canDeleteLanguage_: function(languageCode) {
// First create the set of engines to be removed from input methods
// associated with the language code.
var enginesToBeRemovedSet = {};
var inputMethodIds = this.languageCodeToInputMethodIdsMap_[languageCode];
// If this language doesn't have any input methods, it can be deleted.
if (!inputMethodIds)
return true;
for (var i = 0; i < inputMethodIds.length; i++) {
enginesToBeRemovedSet[inputMethodIds[i]] = true;
}
// Then eliminate engines that are also used for other active languages.
// For instance, if "xkb:us::eng" is used for both English and Filipino.
var languageCodes = $('language-options-list').getLanguageCodes();
for (var i = 0; i < languageCodes.length; i++) {
// Skip the target language code.
if (languageCodes[i] == languageCode) {
continue;
}
// Check if input methods used in this language are included in
// enginesToBeRemovedSet. If so, eliminate these from the set, so
// we don't remove this time.
var inputMethodIdsForAnotherLanguage =
this.languageCodeToInputMethodIdsMap_[languageCodes[i]];
if (!inputMethodIdsForAnotherLanguage)
continue;
for (var j = 0; j < inputMethodIdsForAnotherLanguage.length; j++) {
var inputMethodId = inputMethodIdsForAnotherLanguage[j];
if (inputMethodId in enginesToBeRemovedSet) {
delete enginesToBeRemovedSet[inputMethodId];
}
}
}
// Update the preload engine list with the to-be-removed set.
var newPreloadEngines = [];
for (var i = 0; i < this.preloadEngines_.length; i++) {
if (!(this.preloadEngines_[i] in enginesToBeRemovedSet)) {
newPreloadEngines.push(this.preloadEngines_[i]);
}
}
// Don't allow this operation if it causes the number of preload
// engines to be zero.
return (newPreloadEngines.length > 0);
},
/**
* Saves the enabled extension preference.
* @private
*/
saveEnabledExtensionPref_: function() {
Preferences.setStringPref(ENABLED_EXTENSION_IME_PREF,
this.enabledExtensionImes_.join(','), true);
},
/**
* Updates the checkboxes in the input method list from the enabled
* extensions preference.
* @private
*/
updateCheckboxesFromEnabledExtensions_: function() {
// Convert the list into a dictonary for simpler lookup.
var dictionary = {};
for (var i = 0; i < this.enabledExtensionImes_.length; i++)
dictionary[this.enabledExtensionImes_[i]] = true;
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].inputMethodId.match(/^_ext_ime_/))
checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
}
var configureButtons = inputMethodList.querySelectorAll('button');
for (var i = 0; i < configureButtons.length; i++) {
if (configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
configureButtons[i].hidden =
!(configureButtons[i].inputMethodId in dictionary);
}
}
},
/**
* Updates the enabled extensions preference from the checkboxes in the
* input method list.
* @private
*/
updateEnabledExtensionsFromCheckboxes_: function() {
this.enabledExtensionImes_ = [];
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
if (checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
if (checkboxes[i].checked)
this.enabledExtensionImes_.push(checkboxes[i].inputMethodId);
}
}
},
/**
* Saves the preload engines preference.
* @private
*/
savePreloadEnginesPref_: function() {
Preferences.setStringPref(PRELOAD_ENGINES_PREF,
this.preloadEngines_.join(','), true);
},
/**
* Updates the checkboxes in the input method list from the preload
* engines preference.
* @private
*/
updateCheckboxesFromPreloadEngines_: function() {
// Convert the list into a dictonary for simpler lookup.
var dictionary = {};
for (var i = 0; i < this.preloadEngines_.length; i++) {
dictionary[this.preloadEngines_[i]] = true;
}
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/))
checkboxes[i].checked = (checkboxes[i].inputMethodId in dictionary);
}
var configureButtons = inputMethodList.querySelectorAll('button');
for (var i = 0; i < configureButtons.length; i++) {
if (!configureButtons[i].inputMethodId.match(/^_ext_ime_/)) {
configureButtons[i].hidden =
!(configureButtons[i].inputMethodId in dictionary);
}
}
},
/**
* Updates the preload engines preference from the checkboxes in the
* input method list.
* @private
*/
updatePreloadEnginesFromCheckboxes_: function() {
this.preloadEngines_ = [];
var inputMethodList = $('language-options-input-method-list');
var checkboxes = inputMethodList.querySelectorAll('input');
for (var i = 0; i < checkboxes.length; i++) {
if (!checkboxes[i].inputMethodId.match(/^_ext_ime_/)) {
if (checkboxes[i].checked)
this.preloadEngines_.push(checkboxes[i].inputMethodId);
}
}
var languageOptionsList = $('language-options-list');
languageOptionsList.updateDeletable();
},
/**
* Filters bad preload engines in case bad preload engines are
* stored in the preference. Removes duplicates as well.
* @param {Array} preloadEngines List of preload engines.
* @private
*/
filterBadPreloadEngines_: function(preloadEngines) {
// Convert the list into a dictonary for simpler lookup.
var dictionary = {};
var list = loadTimeData.getValue('inputMethodList');
for (var i = 0; i < list.length; i++) {
dictionary[list[i].id] = true;
}
var enabledPreloadEngines = [];
var seen = {};
for (var i = 0; i < preloadEngines.length; i++) {
// Check if the preload engine is present in the
// dictionary, and not duplicate. Otherwise, skip it.
// Component Extension IME should be handled same as preloadEngines and
// "_comp_" is the special prefix of its ID.
if ((preloadEngines[i] in dictionary && !(preloadEngines[i] in seen)) ||
/^_comp_/.test(preloadEngines[i])) {
enabledPreloadEngines.push(preloadEngines[i]);
seen[preloadEngines[i]] = true;
}
}
return enabledPreloadEngines;
},
// TODO(kochi): This is an adapted copy from new_tab.js.
// If this will go as final UI, refactor this to share the component with
// new new tab page.
/**
* @private
*/
notificationTimeout_: null,
/**
* Shows notification.
* @param {string} text
* @param {string} actionText
* @param {number=} opt_delay
* @private
*/
showNotification_: function(text, actionText, opt_delay) {
var notificationElement = $('notification');
var actionLink = notificationElement.querySelector('.link-color');
var delay = opt_delay || 10000;
function show() {
window.clearTimeout(this.notificationTimeout_);
notificationElement.classList.add('show');
document.body.classList.add('notification-shown');
}
function hide() {
window.clearTimeout(this.notificationTimeout_);
notificationElement.classList.remove('show');
document.body.classList.remove('notification-shown');
// Prevent tabbing to the hidden link.
actionLink.tabIndex = -1;
// Setting tabIndex to -1 only prevents future tabbing to it. If,
// however, the user switches window or a tab and then moves back to
// this tab the element may gain focus. We therefore make sure that we
// blur the element so that the element focus is not restored when
// coming back to this window.
actionLink.blur();
}
function delayedHide() {
this.notificationTimeout_ = window.setTimeout(hide, delay);
}
notificationElement.firstElementChild.textContent = text;
actionLink.textContent = actionText;
actionLink.onclick = hide;
actionLink.onkeydown = function(e) {
if (e.key == 'Enter') {
hide();
}
};
notificationElement.onmouseover = show;
notificationElement.onmouseout = delayedHide;
actionLink.onfocus = show;
actionLink.onblur = delayedHide;
// Enable tabbing to the link now that it is shown.
actionLink.tabIndex = 0;
show();
delayedHide();
},
/**
* Chrome callback for when the UI language preference is saved.
* @param {string} languageCode The newly selected language to use.
* @private
*/
uiLanguageSaved_: function(languageCode) {
this.prospectiveUiLanguageCode_ = languageCode;
// If the user is no longer on the same language code, ignore.
if ($('language-options-list').getSelectedLanguageCode() != languageCode)
return;
// Special case for when a user changes to a different language, and
// changes back to the same language without having restarted Chrome or
// logged in/out of ChromeOS.
if (languageCode == loadTimeData.getString('currentUiLanguageCode')) {
this.updateUiLanguageButton_(languageCode);
return;
}
// Otherwise, show a notification telling the user that their changes will
// only take effect after restart.
showMutuallyExclusiveNodes([$('language-options-ui-language-button'),
$('language-options-ui-notification-bar')],
1);
},
/**
* A handler for when dictionary for |languageCode| begins downloading.
* @param {string} languageCode The language of the dictionary that just
* began downloading.
* @private
*/
onDictionaryDownloadBegin_: function(languageCode) {
this.spellcheckDictionaryDownloadStatus_[languageCode] =
DOWNLOAD_STATUS.IN_PROGRESS;
if (!cr.isMac &&
languageCode ==
$('language-options-list').getSelectedLanguageCode()) {
this.updateSpellCheckLanguageControls_(languageCode);
}
},
/**
* A handler for when dictionary for |languageCode| successfully downloaded.
* @param {string} languageCode The language of the dictionary that
* succeeded downloading.
* @private
*/
onDictionaryDownloadSuccess_: function(languageCode) {
delete this.spellcheckDictionaryDownloadStatus_[languageCode];
this.spellcheckDictionaryDownloadFailures_ = 0;
if (!cr.isMac &&
languageCode ==
$('language-options-list').getSelectedLanguageCode()) {
this.updateSpellCheckLanguageControls_(languageCode);
}
},
/**
* A handler for when dictionary for |languageCode| fails to download.
* @param {string} languageCode The language of the dictionary that failed
* to download.
* @private
*/
onDictionaryDownloadFailure_: function(languageCode) {
this.spellcheckDictionaryDownloadStatus_[languageCode] =
DOWNLOAD_STATUS.FAILED;
this.spellcheckDictionaryDownloadFailures_++;
if (!cr.isMac &&
languageCode ==
$('language-options-list').getSelectedLanguageCode()) {
this.updateSpellCheckLanguageControls_(languageCode);
}
},
/**
* Converts the language code for Translation. There are some differences
* between the language set for Translation and that for Accept-Language.
* @param {string} languageCode The language code like 'fr'.
* @return {string} The converted language code.
* @private
*/
convertLangCodeForTranslation_: function(languageCode) {
var tokens = languageCode.split('-');
var main = tokens[0];
// See also: components/translate/core/browser/common/translate_util.cc
var synonyms = {
'nb': 'no',
'he': 'iw',
'jv': 'jw',
'fil': 'tl',
'zh-HK': 'zh-TW',
'zh-MO': 'zh-TW',
'zh-SG': 'zh-CN',
};
if (main in synonyms) {
return synonyms[main];
} else if (main == 'zh') {
// In Translation, general Chinese is not used, and the sub code is
// necessary as a language code for Translate server.
return languageCode;
}
return main;
},
/**
* Handles activate-ime-menu checkbox's click event.
* @param {Event} e Click event.
* @private
*/
handleActivateImeMenuCheckboxClick_: function(e) {
if (cr.isChromeOS) {
var checkbox = e.target;
Preferences.setBooleanPref(ACTIVATE_IME_MENU_PREF,
checkbox.checked, true);
}
},
/**
* Updates the activate-ime-menu check box's checked state.
* @param {Event} e Change event.
* @private
*/
updateImeMenuCheckbox_: function(e) {
$('activate-ime-menu').checked = e.value.value;
},
};
/**
* Shows the node at |index| in |nodes|, hides all others.
* @param {Array<HTMLElement>} nodes The nodes to be shown or hidden.
* @param {number} index The index of |nodes| to show.
*/
function showMutuallyExclusiveNodes(nodes, index) {
assert(index >= 0 && index < nodes.length);
for (var i = 0; i < nodes.length; ++i) {
assert(nodes[i] instanceof HTMLElement); // TODO(dbeam): Ignore null?
nodes[i].hidden = i != index;
}
}
LanguageOptions.uiLanguageSaved = function(languageCode) {
LanguageOptions.getInstance().uiLanguageSaved_(languageCode);
};
LanguageOptions.onDictionaryDownloadBegin = function(languageCode) {
LanguageOptions.getInstance().onDictionaryDownloadBegin_(languageCode);
};
LanguageOptions.onDictionaryDownloadSuccess = function(languageCode) {
LanguageOptions.getInstance().onDictionaryDownloadSuccess_(languageCode);
};
LanguageOptions.onDictionaryDownloadFailure = function(languageCode) {
LanguageOptions.getInstance().onDictionaryDownloadFailure_(languageCode);
};
// Export
return {
LanguageOptions: LanguageOptions
};
});